New upstream version 1.0
Sophie Brun
4 years ago
0 | [flake8] | |
1 | ignore = W391, E722, E303, E701, W291, E222, E241, E225, E115, E117, E122, E123, E124, E501, E261, E127, E265, F401, W292, W293, W605, E231, E221, E222, F841, E128, W504, E202, F811, E251, E226, E203, E262, E225, E305, E302, E305 | |
2 | exclude= report-collection⏎ |
0 | # Created by .ignore support plugin (hsz.mobi) | |
1 | ### Python template | |
2 | # Byte-compiled / optimized / DLL files | |
3 | __pycache__/ | |
4 | *.py[cod] | |
5 | *$py.class | |
6 | ||
7 | # C extensions | |
8 | *.so | |
9 | ||
10 | # Distribution / packaging | |
11 | .Python | |
12 | build/ | |
13 | develop-eggs/ | |
14 | dist/ | |
15 | downloads/ | |
16 | eggs/ | |
17 | .eggs/ | |
18 | lib/ | |
19 | lib64/ | |
20 | parts/ | |
21 | sdist/ | |
22 | var/ | |
23 | wheels/ | |
24 | pip-wheel-metadata/ | |
25 | share/python-wheels/ | |
26 | *.egg-info/ | |
27 | .installed.cfg | |
28 | *.egg | |
29 | MANIFEST | |
30 | ||
31 | # PyInstaller | |
32 | # Usually these files are written by a python script from a template | |
33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. | |
34 | *.manifest | |
35 | *.spec | |
36 | ||
37 | # Installer logs | |
38 | pip-log.txt | |
39 | pip-delete-this-directory.txt | |
40 | ||
41 | # Unit test / coverage reports | |
42 | htmlcov/ | |
43 | .tox/ | |
44 | .nox/ | |
45 | .coverage | |
46 | .coverage.* | |
47 | .cache | |
48 | nosetests.xml | |
49 | coverage.xml | |
50 | *.cover | |
51 | .hypothesis/ | |
52 | .pytest_cache/ | |
53 | ||
54 | # Translations | |
55 | *.mo | |
56 | *.pot | |
57 | ||
58 | # Django stuff: | |
59 | *.log | |
60 | local_settings.py | |
61 | db.sqlite3 | |
62 | db.sqlite3-journal | |
63 | ||
64 | # Flask stuff: | |
65 | instance/ | |
66 | .webassets-cache | |
67 | ||
68 | # Scrapy stuff: | |
69 | .scrapy | |
70 | ||
71 | # Sphinx documentation | |
72 | docs/_build/ | |
73 | ||
74 | # PyBuilder | |
75 | target/ | |
76 | ||
77 | # Jupyter Notebook | |
78 | .ipynb_checkpoints | |
79 | ||
80 | # IPython | |
81 | profile_default/ | |
82 | ipython_config.py | |
83 | ||
84 | # pyenv | |
85 | .python-version | |
86 | ||
87 | # pipenv | |
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. | |
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies | |
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not | |
91 | # install all needed dependencies. | |
92 | #Pipfile.lock | |
93 | ||
94 | # celery beat schedule file | |
95 | celerybeat-schedule | |
96 | ||
97 | # SageMath parsed files | |
98 | *.sage.py | |
99 | ||
100 | # Environments | |
101 | .env | |
102 | .venv | |
103 | env/ | |
104 | venv/ | |
105 | ENV/ | |
106 | env.bak/ | |
107 | venv.bak/ | |
108 | ||
109 | # Spyder project settings | |
110 | .spyderproject | |
111 | .spyproject | |
112 | ||
113 | # Rope project settings | |
114 | .ropeproject | |
115 | ||
116 | # mkdocs documentation | |
117 | /site | |
118 | ||
119 | # mypy | |
120 | .mypy_cache/ | |
121 | .dmypy.json | |
122 | dmypy.json | |
123 | ||
124 | # Pyre type checker | |
125 | .pyre/ | |
126 | ||
127 | .idea | |
128 | /report-collection/ |
0 | stages: | |
1 | - pre_testing | |
2 | - testing | |
3 | ||
4 | before_script: | |
5 | - apt-get update -qy | |
6 | - pip install pip -U | |
7 | ||
8 | flake8: | |
9 | image: python:3 | |
10 | stage: pre_testing | |
11 | before_script: | |
12 | - pip install flake8 | |
13 | # Help flake8 to find the Python files without .py extension. | |
14 | - find * -type f -name '*.py' > files.txt | |
15 | - find * -type f -print0 | xargs -0 file | grep 'Python script' | cut -d':' -f1 >> files.txt | |
16 | - sort -u files.txt | tee files.processed | |
17 | script: | |
18 | - python -m flake8 --statistics --count $(cat files.processed) | |
19 | after_script: | |
20 | - wc -l files.processed | |
21 | ||
22 | tests: | |
23 | image: python:3 | |
24 | stage: testing | |
25 | coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/' | |
26 | before_script: | |
27 | - pip3 install virtualenv | |
28 | - virtualenv -p python3 faraday_venv | |
29 | - source faraday_venv/bin/activate | |
30 | - 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 | |
33 | script: | |
34 | - cd - | |
35 | - source faraday_venv/bin/activate | |
36 | - python3 setup.py install | |
37 | - cd run_from && pytest ../tests --capture=sys -v --cov=faraday_plugins --color=yes --disable-warnings⏎ |
0 | include faraday_plugins/plugins/port_mapper.txt⏎ |
0 | ## Install | |
1 | ||
2 | ```shell script | |
3 | cd faraday-plugins | |
4 | python setup.py install | |
5 | ``` | |
6 | ||
7 | ## Commands | |
8 | ||
9 | > List Plugins | |
10 | ||
11 | ```shell script | |
12 | python -m faraday_plugins list | |
13 | ``` | |
14 | ||
15 | > Test autodetect plugin from report | |
16 | ||
17 | ```shell script | |
18 | python -m faraday_plugins detect /path/to/report.xml | |
19 | ``` | |
20 | ||
21 | ||
22 | > Test report with plugin | |
23 | ||
24 | ```shell script | |
25 | python -m faraday_plugins process appscan /path/to/report.xml | |
26 | ``` | |
27 |
0 | import os | |
1 | import click | |
2 | from .plugins.manager import PluginsManager, ReportAnalyzer | |
3 | ||
4 | ||
5 | @click.group() | |
6 | def cli(): | |
7 | pass | |
8 | ||
9 | ||
10 | @cli.command() | |
11 | def list(): | |
12 | plugins_manager = PluginsManager() | |
13 | click.echo("Available Plugins:") | |
14 | loaded_plugins = 0 | |
15 | for plugin_id, plugin in plugins_manager.get_plugins(): | |
16 | click.echo(f"{plugin.id} - {plugin.name}") | |
17 | loaded_plugins += 1 | |
18 | click.echo(f"Loaded Plugins: {loaded_plugins}") | |
19 | ||
20 | ||
21 | @cli.command() | |
22 | @click.argument('plugin_id') | |
23 | @click.argument('report_file') | |
24 | def process(plugin_id, report_file): | |
25 | if not os.path.isfile(report_file): | |
26 | click.echo(f"File {report_file} Don't Exists") | |
27 | else: | |
28 | plugins_manager = PluginsManager() | |
29 | plugin = plugins_manager.get_plugin(plugin_id) | |
30 | if plugin: | |
31 | plugin.processReport(report_file) | |
32 | click.echo(plugin.get_json()) | |
33 | else: | |
34 | click.echo(f"Unknown Plugin: {plugin_id}") | |
35 | ||
36 | ||
37 | @cli.command() | |
38 | @click.argument('report_file') | |
39 | def detect(report_file): | |
40 | if not os.path.isfile(report_file): | |
41 | click.echo(f"File {report_file} Don't Exists") | |
42 | else: | |
43 | plugins_manager = PluginsManager() | |
44 | analyzer = ReportAnalyzer(plugins_manager) | |
45 | plugin = analyzer.get_plugin(report_file) | |
46 | if plugin: | |
47 | click.echo(plugin) | |
48 | else: | |
49 | click.echo(f"Failed to detect") | |
50 | ||
51 | ||
52 | if __name__ == "__main__": | |
53 | cli() |
0 | import logging | |
1 | import traceback | |
2 | import re | |
3 | import os | |
4 | import sys | |
5 | import pkgutil | |
6 | from importlib import import_module | |
7 | from importlib.machinery import SourceFileLoader | |
8 | ||
9 | from . import repo | |
10 | ||
11 | logger = logging.getLogger("faraday").getChild(__name__) | |
12 | ||
13 | try: | |
14 | import xml.etree.cElementTree as ET | |
15 | except ImportError: | |
16 | logger.warning("cElementTree could not be imported. Using ElementTree instead") | |
17 | import xml.etree.ElementTree as ET | |
18 | ||
19 | ||
20 | class ReportAnalyzer: | |
21 | ||
22 | def __init__(self, plugin_manager): | |
23 | self.plugin_manager = plugin_manager | |
24 | ||
25 | def get_plugin(self, report_path): | |
26 | plugin = None | |
27 | if not os.path.isfile(report_path): | |
28 | logger.error("Report [%s] don't exists", report_path) | |
29 | return plugin | |
30 | else: | |
31 | file_name = os.path.basename(report_path) | |
32 | plugin = self._get_plugin_by_name(file_name) | |
33 | if not plugin: # Was unable to detect plugin from report file name | |
34 | logger.debug("Plugin by name not found") | |
35 | plugin = self._get_plugin_by_file_type(report_path) | |
36 | if not plugin: | |
37 | logger.debug("Plugin by file not found") | |
38 | if not plugin: | |
39 | logger.debug("Plugin for file (%s) not found", report_path) | |
40 | return plugin | |
41 | ||
42 | def _get_plugin_by_name(self, file_name_base): | |
43 | plugin_id = None | |
44 | plugin_name_regex = r".*_faraday_(?P<plugin_name>.+)\..*$" | |
45 | match = re.match(plugin_name_regex, file_name_base) | |
46 | if match: | |
47 | plugin_id = match.groupdict()['plugin_name'].lower() | |
48 | logger.debug("Plugin name match: %s", plugin_id) | |
49 | plugin = self.plugin_manager.get_plugin(plugin_id) | |
50 | if plugin: | |
51 | logger.debug("Plugin by name Found: %s", plugin.id) | |
52 | return plugin | |
53 | else: | |
54 | logger.debug("Invalid plugin from file name: %s", plugin_id) | |
55 | return None | |
56 | else: | |
57 | logger.debug("Could not extract plugin_id from filename: %s", file_name_base) | |
58 | return plugin_id | |
59 | ||
60 | def _get_plugin_by_file_type(self, report_path): | |
61 | plugin = None | |
62 | file_name = os.path.basename(report_path) | |
63 | file_name_base, file_extension = os.path.splitext(file_name) | |
64 | file_extension = file_extension.lower() | |
65 | main_tag = None | |
66 | logger.debug("Analyze report File") | |
67 | # Try to parse as xml | |
68 | try: | |
69 | report_file = open(report_path, "rb") | |
70 | except Exception as e: | |
71 | logger.error("Error reading report content [%s]", e) | |
72 | else: | |
73 | try: | |
74 | for event, elem in ET.iterparse(report_file, ('start',)): | |
75 | main_tag = elem.tag | |
76 | break | |
77 | logger.debug("Found XML content on file: %s - Main tag: %s", report_path, main_tag) | |
78 | except Exception as e: | |
79 | logger.debug("Non XML content [%s] - %s", report_path, e) | |
80 | finally: | |
81 | report_file.close() | |
82 | for _plugin_id, _plugin in self.plugin_manager.get_plugins(): | |
83 | logger.debug("Try: %s", _plugin_id) | |
84 | try: | |
85 | if _plugin.report_belongs_to(main_tag=main_tag, report_path=report_path, extension=file_extension): | |
86 | plugin = _plugin | |
87 | logger.debug("Plugin by File Found: %s", plugin.id) | |
88 | break | |
89 | except Exception as e: | |
90 | logger.error("Error in plugin analysis: (%s) %s", _plugin_id, e) | |
91 | return plugin | |
92 | ||
93 | ||
94 | class PluginsManager: | |
95 | ||
96 | def __init__(self): | |
97 | self.plugins = {} | |
98 | self.plugin_modules = {} | |
99 | self._load_plugins() | |
100 | ||
101 | def _load_plugins(self): | |
102 | logger.info("Loading Native Plugins...") | |
103 | if not self.plugins: | |
104 | for _, name, _ in filter(lambda x: x[2], pkgutil.iter_modules(repo.__path__)): | |
105 | try: | |
106 | plugin_module = import_module(f"faraday_plugins.plugins.repo.{name}.plugin") | |
107 | if hasattr(plugin_module, "createPlugin"): | |
108 | plugin_instance = plugin_module.createPlugin() | |
109 | plugin_id = plugin_instance.id.lower() | |
110 | if plugin_id not in self.plugin_modules: | |
111 | self.plugin_modules[plugin_id] = plugin_module | |
112 | logger.debug("Load Plugin [%s]", name) | |
113 | else: | |
114 | logger.debug("Plugin already loaded [%s]", plugin_id) | |
115 | else: | |
116 | logger.error("Invalid Plugin [%s]", name) | |
117 | except Exception as e: | |
118 | logger.error("Cant load plugin module: %s [%s]", name, e) | |
119 | try: | |
120 | import faraday.server.config | |
121 | if os.path.isdir(faraday.server.config.faraday_server.custom_plugins_folder): | |
122 | logger.info("Loading Custom Plugins...") | |
123 | dir_name_regexp = re.compile(r"^[\d\w\-\_]+$") | |
124 | for name in os.listdir(faraday.server.config.faraday_server.custom_plugins_folder): | |
125 | if dir_name_regexp.match(name) and name != "__pycache__": | |
126 | try: | |
127 | module_path = os.path.join(faraday.server.config.faraday_server.custom_plugins_folder, | |
128 | name) | |
129 | sys.path.append(module_path) | |
130 | module_filename = os.path.join(module_path, "plugin.py") | |
131 | file_ext = os.path.splitext(module_filename)[1] | |
132 | if file_ext.lower() == '.py': | |
133 | if name not in self.plugin_modules: | |
134 | loader = SourceFileLoader(name, module_filename) | |
135 | plugin_module = loader.load_module() | |
136 | plugin_instance = plugin_module.createPlugin() | |
137 | plugin_id = plugin_instance.id.lower() | |
138 | if plugin_id not in self.plugin_modules: | |
139 | self.plugin_modules[plugin_id] = plugin_module | |
140 | else: | |
141 | logger.debug("Plugin with same name already loaded [%s]", name) | |
142 | logger.debug('Loading plugin {0}'.format(name)) | |
143 | except Exception as e: | |
144 | logger.debug("An error ocurred while loading plugin %s.\n%s", module_filename, | |
145 | traceback.format_exc()) | |
146 | logger.warning(e) | |
147 | except Exception as e: | |
148 | logger.info("Can't import faraday server, no custom plugins will be loaded") | |
149 | logger.info("%s plugins loaded", len(self.plugin_modules)) | |
150 | ||
151 | def get_plugin(self, plugin_id): | |
152 | plugin = None | |
153 | plugin_id = plugin_id.lower() | |
154 | if plugin_id in self.plugin_modules: | |
155 | plugin = self.plugin_modules[plugin_id].createPlugin() | |
156 | else: | |
157 | logger.debug("Unknown Plugin: %s", plugin_id) | |
158 | return plugin | |
159 | ||
160 | def get_plugins(self): | |
161 | for plugin_id, plugin_module in self.plugin_modules.items(): | |
162 | logger.debug("Instance Plugin: %s", plugin_id) | |
163 | yield plugin_id, plugin_module.createPlugin() |
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 | import os | |
7 | import re | |
8 | import uuid | |
9 | import logging | |
10 | import simplejson as json | |
11 | from datetime import datetime | |
12 | ||
13 | ||
14 | logger = logging.getLogger("faraday").getChild(__name__) | |
15 | ||
16 | ||
17 | class PluginBase: | |
18 | # TODO: Add class generic identifier | |
19 | class_signature = "PluginBase" | |
20 | ||
21 | def __init__(self): | |
22 | # Must be unique. Check that there is not | |
23 | # an existant plugin with the same id. | |
24 | # TODO: Make script that list current ids. | |
25 | self.id = None | |
26 | self._rid = id(self) | |
27 | self.version = None | |
28 | self.name = None | |
29 | self.description = "" | |
30 | self._command_regex = None | |
31 | self._output_file_path = None | |
32 | self.framework_version = None | |
33 | self._completition = {} | |
34 | self._new_elems = [] | |
35 | self._settings = {} | |
36 | self.command_id = None | |
37 | self.cache = {} | |
38 | self._hosts_cache = {} | |
39 | self.start_date = datetime.now() | |
40 | self.logger = logger.getChild(self.__class__.__name__) | |
41 | self.open_options = {"mode": "r", "encoding": "utf-8"} | |
42 | self.vulns_data = {"hosts": [], "command": {"tool": "", | |
43 | "command": "", | |
44 | "params": "", | |
45 | "user": "", | |
46 | "hostname": "", | |
47 | "start_date": self.start_date.isoformat(), | |
48 | "duration": 0, | |
49 | "import_source": "report"}} | |
50 | ||
51 | def __str__(self): | |
52 | return f"Plugin: {self.id}" | |
53 | ||
54 | @staticmethod | |
55 | def normalize_severity(severity): | |
56 | if severity is not None: | |
57 | severity = str(severity).lower() | |
58 | else: | |
59 | severity = "" | |
60 | ||
61 | def align_string_based_vulns(severity): | |
62 | severities = ['info', 'low', 'med', 'high', 'critical'] | |
63 | for sev in severities: | |
64 | if severity[0:3] in sev: | |
65 | return sev | |
66 | return severity | |
67 | severity = align_string_based_vulns(severity) | |
68 | # Transform numeric severity into desc severity | |
69 | numeric_severities = {"0": "info", | |
70 | "1": "low", | |
71 | "2": "med", | |
72 | "3": "high", | |
73 | "4": "critical"} | |
74 | if severity not in numeric_severities.values(): | |
75 | severity = numeric_severities.get(severity, 'unclassified') | |
76 | return severity | |
77 | ||
78 | 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']}" | |
83 | if cache_id not in self._hosts_cache: | |
84 | obj_uuid = self.save_cache(obj) | |
85 | self.vulns_data["hosts"].append(obj) | |
86 | self._hosts_cache[cache_id] = obj_uuid | |
87 | else: | |
88 | obj_uuid = self._hosts_cache[cache_id] | |
89 | return obj_uuid | |
90 | ||
91 | def save_cache(self, obj): | |
92 | obj_uuid = uuid.uuid1() | |
93 | self.cache[obj_uuid] = obj | |
94 | return obj_uuid | |
95 | ||
96 | def report_belongs_to(self, **kwargs): | |
97 | return False | |
98 | ||
99 | def has_custom_output(self): | |
100 | return bool(self._output_file_path) | |
101 | ||
102 | def get_custom_file_path(self): | |
103 | return self._output_file_path | |
104 | ||
105 | def set_actions_queue(self, _pending_actions): | |
106 | """ | |
107 | We use plugin controller queue to add actions created by plugins. | |
108 | Plugin controller will consume this actions. | |
109 | ||
110 | :param controller: plugin controller | |
111 | :return: None | |
112 | """ | |
113 | self._pending_actions = _pending_actions | |
114 | ||
115 | def setCommandID(self, command_id): | |
116 | self.command_id = command_id | |
117 | ||
118 | def getSettings(self): | |
119 | for param, (param_type, value) in self._settings.items(): | |
120 | yield param, value | |
121 | ||
122 | def get_ws(self): # TODO Borrar | |
123 | return "" | |
124 | ||
125 | def getSetting(self, name): | |
126 | setting_type, value = self._settings[name] | |
127 | return value | |
128 | ||
129 | def addSetting(self, param, param_type, value): | |
130 | self._settings[param] = param_type, value | |
131 | ||
132 | def updateSettings(self, new_settings): | |
133 | for name, value in new_settings.items(): | |
134 | if name in self._settings: | |
135 | setting_type, curr_value = self._settings[name] | |
136 | self._settings[name] = setting_type, setting_type(value) | |
137 | ||
138 | def canParseCommandString(self, current_input): | |
139 | """ | |
140 | This method can be overriden in the plugin implementation | |
141 | if a different kind of check is needed | |
142 | """ | |
143 | return (self._command_regex is not None and | |
144 | self._command_regex.match(current_input.strip()) is not None) | |
145 | ||
146 | def getCompletitionSuggestionsList(self, current_input): | |
147 | """ | |
148 | This method can be overriden in the plugin implementation | |
149 | if a different kind of check is needed | |
150 | """ | |
151 | words = current_input.split(" ") | |
152 | cword = words[len(words) - 1] | |
153 | options = {} | |
154 | for k, v in self._completition.items(): | |
155 | if re.search(str("^" + cword), k, flags=re.IGNORECASE): | |
156 | options[k] = v | |
157 | return options | |
158 | ||
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()): | |
162 | self._parse_filename(self.get_custom_file_path()) | |
163 | else: | |
164 | self.parseOutputString(output) | |
165 | ||
166 | def _parse_filename(self, filename): | |
167 | with open(filename, **self.open_options) as output: | |
168 | self.parseOutputString(output.read()) | |
169 | ||
170 | def processReport(self, filepath, user="faraday"): | |
171 | if os.path.isfile(filepath): | |
172 | self._parse_filename(filepath) | |
173 | self.vulns_data["command"]["params"] = filepath | |
174 | self.vulns_data["command"]["user"] = user | |
175 | else: | |
176 | raise FileNotFoundError(filepath) | |
177 | ||
178 | def parseOutputString(self, output): | |
179 | """ | |
180 | This method must be implemented. | |
181 | This method will be called when the command finished executing and | |
182 | the complete output will be received to work with it | |
183 | Using the output the plugin can create and add hosts, interfaces, | |
184 | services, etc. | |
185 | """ | |
186 | raise NotImplementedError('This method must be implemented.') | |
187 | ||
188 | ||
189 | def createAndAddHost(self, name, os="unknown", hostnames=None, mac=None): | |
190 | if not hostnames: | |
191 | hostnames = [] | |
192 | if os is None: | |
193 | os = "unknown" | |
194 | host = {"ip": name, "os": os, "hostnames": hostnames, "description": "", "mac": mac, | |
195 | "credentials": [], "services": [], "vulnerabilities": [], | |
196 | } | |
197 | host_id = self.save_host_cache(host) | |
198 | return host_id | |
199 | ||
200 | # @deprecation.deprecated(deprecated_in="3.0", removed_in="3.5", | |
201 | # current_version=VERSION, | |
202 | # details="Interface object removed. Use host or service instead") | |
203 | def createAndAddInterface( | |
204 | self, host_id, name="", mac="00:00:00:00:00:00", | |
205 | ipv4_address="0.0.0.0", ipv4_mask="0.0.0.0", ipv4_gateway="0.0.0.0", | |
206 | ipv4_dns=None, ipv6_address="0000:0000:0000:0000:0000:0000:0000:0000", | |
207 | ipv6_prefix="00", | |
208 | ipv6_gateway="0000:0000:0000:0000:0000:0000:0000:0000", ipv6_dns=None, | |
209 | network_segment="", hostname_resolution=None): | |
210 | if ipv4_dns is None: | |
211 | ipv4_dns = [] | |
212 | if ipv6_dns is None: | |
213 | ipv6_dns = [] | |
214 | if hostname_resolution is None: | |
215 | hostname_resolution = [] | |
216 | if not isinstance(hostname_resolution, list): | |
217 | self.logger.warning("hostname_resolution parameter must be a list and is (%s)", type(hostname_resolution)) | |
218 | hostname_resolution = [hostname_resolution] | |
219 | # We don't use interface anymore, so return a host id to maintain | |
220 | # backwards compatibility | |
221 | # Little hack because we dont want change all the plugins for add hostnames in Host object. | |
222 | # SHRUG | |
223 | host = self.get_from_cache(host_id) | |
224 | for hostname in hostname_resolution: | |
225 | if hostname not in host["hostnames"]: | |
226 | host["hostnames"].append(hostname) | |
227 | host["mac"] = mac | |
228 | return host_id | |
229 | ||
230 | # @deprecation.deprecated(deprecated_in="3.0", removed_in="3.5", | |
231 | # current_version=VERSION, | |
232 | # details="Interface object removed. Use host or service instead. Service will be attached to Host!") | |
233 | def createAndAddServiceToInterface(self, host_id, interface_id, name, | |
234 | protocol="tcp?", ports=None, | |
235 | status="open", version="unknown", | |
236 | description=""): | |
237 | return self.createAndAddServiceToHost(host_id, name, protocol, ports, status, version, description) | |
238 | ||
239 | def createAndAddServiceToHost(self, host_id, name, | |
240 | protocol="tcp?", ports=None, | |
241 | status="open", version="unknown", | |
242 | description=""): | |
243 | if ports: | |
244 | if isinstance(ports, list): | |
245 | ports = int(ports[0]) | |
246 | elif isinstance(ports, str): | |
247 | ports = int(ports) | |
248 | ||
249 | if status not in ("open", "closed", "filtered"): | |
250 | self.logger.warning('Unknown service status %s. Using "open" instead', status) | |
251 | status = 'open' | |
252 | service = {"name": name, "protocol": protocol, "port": ports, "status": status, | |
253 | "version": version, "description": description, "credentials": [], "vulnerabilities": []} | |
254 | host = self.get_from_cache(host_id) | |
255 | host["services"].append(service) | |
256 | service_id = self.save_cache(service) | |
257 | return service_id | |
258 | ||
259 | def createAndAddVulnToHost(self, host_id, name, desc="", ref=None, | |
260 | severity="", resolution="", data="", external_id=None): | |
261 | if ref is None: | |
262 | ref = [] | |
263 | vulnerability = {"name": name, "desc": desc, "severity": self.normalize_severity(severity), "refs": ref, "external_id": external_id, | |
264 | "type": "Vulnerability", "resolution": resolution, "data": data} | |
265 | host = self.get_from_cache(host_id) | |
266 | host["vulnerabilities"].append(vulnerability) | |
267 | vulnerability_id = len(host["vulnerabilities"]) - 1 | |
268 | return vulnerability_id | |
269 | ||
270 | # @deprecation.deprecated(deprecated_in="3.0", removed_in="3.5", | |
271 | # current_version=VERSION, | |
272 | # details="Interface object removed. Use host or service instead. Vuln will be added to Host") | |
273 | def createAndAddVulnToInterface(self, host_id, interface_id, name, | |
274 | desc="", ref=None, severity="", | |
275 | resolution="", data=""): | |
276 | return self.createAndAddVulnToHost(host_id, name, desc=desc, ref=ref, severity=severity, | |
277 | resolution=resolution, data=data) | |
278 | ||
279 | def createAndAddVulnToService(self, host_id, service_id, name, desc="", | |
280 | ref=None, severity="", resolution="", data="", external_id=None): | |
281 | if ref is None: | |
282 | ref = [] | |
283 | vulnerability = {"name": name, "desc": desc, "severity": self.normalize_severity(severity), "refs": ref, "external_id": external_id, | |
284 | "type": "Vulnerability", "resolution": resolution, "data": data} | |
285 | service = self.get_from_cache(service_id) | |
286 | service["vulnerabilities"].append(vulnerability) | |
287 | vulnerability_id = self.save_cache(vulnerability) | |
288 | return vulnerability_id | |
289 | ||
290 | def createAndAddVulnWebToService(self, host_id, service_id, name, desc="", | |
291 | ref=None, severity="", resolution="", | |
292 | website="", path="", request="", | |
293 | response="", method="", pname="", | |
294 | params="", query="", category="", data="", external_id=None): | |
295 | if params is None: | |
296 | params = "" | |
297 | if response is None: | |
298 | response = "" | |
299 | if method is None: | |
300 | method = "" | |
301 | if pname is None: | |
302 | pname = "" | |
303 | if params is None: | |
304 | params = "" | |
305 | if query is None: | |
306 | query = "" | |
307 | if ref is None: | |
308 | ref = [] | |
309 | vulnerability = {"name": name, "desc": desc, "severity": self.normalize_severity(severity), "refs": ref, "external_id": external_id, | |
310 | "type": "VulnerabilityWeb", "resolution": resolution, "data": data, "website": website, | |
311 | "path": path, "request": request, "response": response, "method": method, "pname": pname, | |
312 | "params": params, "query": query, "category": category} | |
313 | service = self.get_from_cache(service_id) | |
314 | service["vulnerabilities"].append(vulnerability) | |
315 | vulnerability_id = self.save_cache(vulnerability) | |
316 | return vulnerability_id | |
317 | ||
318 | ||
319 | def createAndAddNoteToHost(self, host_id, name, text): | |
320 | return None | |
321 | ||
322 | def createAndAddNoteToInterface(self, host_id, interface_id, name, text): | |
323 | return None | |
324 | ||
325 | def createAndAddNoteToService(self, host_id, service_id, name, text): | |
326 | return None | |
327 | ||
328 | def createAndAddNoteToNote(self, host_id, service_id, note_id, name, text): | |
329 | return None | |
330 | ||
331 | def createAndAddCredToService(self, host_id, service_id, username, | |
332 | password): | |
333 | credential = {"name": "credential", "username": username, "password": password} | |
334 | service = self.get_from_cache(service_id) | |
335 | service["credentials"].append(credential) | |
336 | credential_id = self.save_cache(credential) | |
337 | return credential_id | |
338 | ||
339 | def log(self, msg, level='INFO'):# TODO borrar | |
340 | pass | |
341 | #self.__addPendingAction(Modelactions.LOG, msg, level) | |
342 | ||
343 | def devlog(self, msg): # TODO borrar | |
344 | pass | |
345 | #self.__addPendingAction(Modelactions.DEVLOG, msg) | |
346 | ||
347 | def get_data(self): | |
348 | self.vulns_data["command"]["tool"] = self.id | |
349 | self.vulns_data["command"]["command"] = self.id | |
350 | self.vulns_data["command"]["duration"] = (datetime.now() - self.start_date).microseconds | |
351 | return self.vulns_data | |
352 | ||
353 | def get_json(self): | |
354 | self.logger.debug("Generate Json") | |
355 | return json.dumps(self.get_data()) | |
356 | ||
357 | class PluginTerminalOutput(PluginBase): | |
358 | def __init__(self): | |
359 | super().__init__() | |
360 | ||
361 | def processOutput(self, term_output): | |
362 | try: | |
363 | self.parseOutputString(term_output) | |
364 | except Exception as e: | |
365 | self.logger.error(e) | |
366 | ||
367 | ||
368 | class PluginCustomOutput(PluginBase): | |
369 | def __init__(self): | |
370 | super().__init__() | |
371 | ||
372 | def processOutput(self, term_output): | |
373 | # we discard the term_output since it's not necessary | |
374 | # for this type of plugins | |
375 | self.processReport(self._output_file_path) | |
376 | ||
377 | ||
378 | class PluginByExtension(PluginBase): | |
379 | def __init__(self): | |
380 | super().__init__() | |
381 | self.extension = [] | |
382 | ||
383 | def report_belongs_to(self, extension="", **kwargs): | |
384 | match = False | |
385 | if type(self.extension) == str: | |
386 | match = (self.extension == extension) | |
387 | elif type(self.extension) == list: | |
388 | match = (extension in self.extension) | |
389 | self.logger.debug("Extension Match: [%s =/in %s] -> %s", extension, self.extension, match) | |
390 | return match | |
391 | ||
392 | ||
393 | class PluginXMLFormat(PluginByExtension): | |
394 | ||
395 | def __init__(self): | |
396 | super().__init__() | |
397 | self.identifier_tag = [] | |
398 | self.extension = ".xml" | |
399 | self.open_options = {"mode": "rb"} | |
400 | ||
401 | def report_belongs_to(self, main_tag="", **kwargs): | |
402 | match = False | |
403 | if super().report_belongs_to(**kwargs): | |
404 | if type(self.identifier_tag) == str: | |
405 | match = (main_tag == self.identifier_tag) | |
406 | elif type(self.identifier_tag) == list: | |
407 | match = (main_tag in self.identifier_tag) | |
408 | self.logger.debug("Tag Match: [%s =/in %s] -> %s", main_tag, self.identifier_tag, match) | |
409 | return match | |
410 | ||
411 | ||
412 | class PluginJsonFormat(PluginByExtension): | |
413 | ||
414 | def __init__(self): | |
415 | super().__init__() | |
416 | self.json_keys = set() | |
417 | self.extension = ".json" | |
418 | ||
419 | def report_belongs_to(self, **kwargs): | |
420 | match = False | |
421 | if super().report_belongs_to(**kwargs): | |
422 | pass | |
423 | return match | |
424 | ||
425 | ||
426 | ||
427 | # 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 | import os | |
7 | import logging | |
8 | import faraday_plugins | |
9 | from urllib.parse import urlsplit | |
10 | ||
11 | ||
12 | SERVICE_MAPPER = None | |
13 | ||
14 | logger = logging.getLogger(__name__) | |
15 | ||
16 | ||
17 | def get_vulnweb_url_fields(url): | |
18 | """Given a URL, return kwargs to pass to createAndAddVulnWebToService.""" | |
19 | parse = urlsplit(url) | |
20 | return { | |
21 | "website": "{}://{}".format(parse.scheme, parse.netloc), | |
22 | "path": parse.path, | |
23 | "query": parse.query | |
24 | } | |
25 | ||
26 | def filter_services(): | |
27 | global SERVICE_MAPPER | |
28 | if not SERVICE_MAPPER: | |
29 | logger.debug("Load service mappers") | |
30 | filename = os.path.join(os.path.dirname(__file__), "port_mapper.txt") | |
31 | with open(filename, encoding='utf-8') as fp: | |
32 | SERVICE_MAPPER = list(map(lambda x: x.strip().split('\t'), list(filter(len, fp.readlines())))) | |
33 | return SERVICE_MAPPER | |
34 | ||
35 | ||
36 | def get_all_protocols(): | |
37 | protocols = [ | |
38 | 'ip', | |
39 | 'tcp', | |
40 | 'udp', | |
41 | 'icmp', | |
42 | 'sctp', | |
43 | 'hopopt', | |
44 | 'igmp', | |
45 | 'ggp', | |
46 | 'ip-encap', | |
47 | 'st', | |
48 | 'egp', | |
49 | 'igp', | |
50 | 'pup', | |
51 | 'hmp', | |
52 | 'xns-idp', | |
53 | 'rdp', | |
54 | 'iso-tp4', | |
55 | 'dccp', | |
56 | 'xtp', | |
57 | 'ddp', | |
58 | 'idpr-cmtp', | |
59 | 'ipv6', | |
60 | 'ipv6-route', | |
61 | 'ipv6-frag', | |
62 | 'idrp', | |
63 | 'rsvp', | |
64 | 'gre', | |
65 | 'ipsec-esp', | |
66 | 'ipsec-ah', | |
67 | 'skip', | |
68 | 'ipv6-icmp', | |
69 | 'ipv6-nonxt', | |
70 | 'ipv6-opts', | |
71 | 'rspf cphb', | |
72 | 'vmtp', | |
73 | 'eigrp', | |
74 | 'ospfigp', | |
75 | 'ax.25', | |
76 | 'ipip', | |
77 | 'etherip', | |
78 | 'encap', | |
79 | 'pim', | |
80 | 'ipcomp', | |
81 | 'vrrp', | |
82 | 'l2tp', | |
83 | 'isis', | |
84 | 'fc', | |
85 | 'udplite', | |
86 | 'mpls-in-ip', | |
87 | 'hip', | |
88 | 'shim6', | |
89 | 'wesp', | |
90 | 'rohc', | |
91 | 'mobility-header' | |
92 | ] | |
93 | ||
94 | for item in protocols: | |
95 | yield item | |
96 | ||
97 | ||
98 | # I'm Py3 |
0 | 1/tcp tcpmux | |
1 | 7/tcp echo | |
2 | 7/udp echo | |
3 | 9/tcp discard | |
4 | 9/udp discard | |
5 | 11/tcp systat | |
6 | 13/tcp daytime | |
7 | 13/udp daytime | |
8 | 15/tcp netstat | |
9 | 17/tcp qotd | |
10 | 18/tcp msp | |
11 | 18/udp msp | |
12 | 19/tcp chargen | |
13 | 19/udp chargen | |
14 | 20/tcp ftp-data | |
15 | 21/tcp ftp | |
16 | 21/udp fsp | |
17 | 22/tcp ssh | |
18 | 22/udp ssh | |
19 | 23/tcp telnet | |
20 | 25/tcp smtp | |
21 | 37/tcp time | |
22 | 37/udp time | |
23 | 39/udp rlp | |
24 | 42/tcp nameserver | |
25 | 43/tcp whois | |
26 | 49/tcp tacacs | |
27 | 49/udp tacacs | |
28 | 50/tcp re-mail-ck | |
29 | 50/udp re-mail-ck | |
30 | 53/tcp domain | |
31 | 53/udp domain | |
32 | 57/tcp mtp | |
33 | 65/tcp tacacs-ds | |
34 | 65/udp tacacs-ds | |
35 | 67/tcp bootps | |
36 | 67/udp bootps | |
37 | 68/tcp bootpc | |
38 | 68/udp bootpc | |
39 | 69/udp tftp | |
40 | 70/tcp gopher | |
41 | 70/udp gopher | |
42 | 77/tcp rje | |
43 | 79/tcp finger | |
44 | 80/tcp http | |
45 | 80/udp http | |
46 | 87/tcp link | |
47 | 88/tcp kerberos | |
48 | 88/udp kerberos | |
49 | 95/tcp supdup | |
50 | 101/tcp hostnames | |
51 | 102/tcp iso-tsap | |
52 | 104/tcp acr-nema | |
53 | 104/udp acr-nema | |
54 | 105/tcp csnet-ns | |
55 | 105/udp csnet-ns | |
56 | 107/tcp rtelnet | |
57 | 107/udp rtelnet | |
58 | 109/tcp pop2 | |
59 | 109/udp pop2 | |
60 | 110/tcp pop3 | |
61 | 110/udp pop3 | |
62 | 111/tcp sunrpc | |
63 | 111/udp sunrpc | |
64 | 113/tcp auth | |
65 | 115/tcp sftp | |
66 | 117/tcp uucp-path | |
67 | 119/tcp nntp | |
68 | 123/tcp ntp | |
69 | 123/udp ntp | |
70 | 129/tcp pwdgen | |
71 | 129/udp pwdgen | |
72 | 135/tcp loc-srv | |
73 | 135/udp loc-srv | |
74 | 137/tcp netbios-ns | |
75 | 137/udp netbios-ns | |
76 | 138/tcp netbios-dgm | |
77 | 138/udp netbios-dgm | |
78 | 139/tcp netbios-ssn | |
79 | 139/udp netbios-ssn | |
80 | 143/tcp imap2 | |
81 | 143/udp imap2 | |
82 | 161/tcp snmp | |
83 | 161/udp snmp | |
84 | 162/tcp snmp-trap | |
85 | 162/udp snmp-trap | |
86 | 163/tcp cmip-man | |
87 | 163/udp cmip-man | |
88 | 164/tcp cmip-agent | |
89 | 164/udp cmip-agent | |
90 | 174/tcp mailq | |
91 | 174/udp mailq | |
92 | 177/tcp xdmcp | |
93 | 177/udp xdmcp | |
94 | 178/tcp nextstep | |
95 | 178/udp nextstep | |
96 | 179/tcp bgp | |
97 | 179/udp bgp | |
98 | 191/tcp prospero | |
99 | 191/udp prospero | |
100 | 194/tcp irc | |
101 | 194/udp irc | |
102 | 199/tcp smux | |
103 | 199/udp smux | |
104 | 201/tcp at-rtmp | |
105 | 201/udp at-rtmp | |
106 | 202/tcp at-nbp | |
107 | 202/udp at-nbp | |
108 | 204/tcp at-echo | |
109 | 204/udp at-echo | |
110 | 206/tcp at-zis | |
111 | 206/udp at-zis | |
112 | 209/tcp qmtp | |
113 | 209/udp qmtp | |
114 | 210/tcp z3950 | |
115 | 210/udp z3950 | |
116 | 213/tcp ipx | |
117 | 213/udp ipx | |
118 | 220/tcp imap3 | |
119 | 220/udp imap3 | |
120 | 345/tcp pawserv | |
121 | 345/udp pawserv | |
122 | 346/tcp zserv | |
123 | 346/udp zserv | |
124 | 347/tcp fatserv | |
125 | 347/udp fatserv | |
126 | 369/tcp rpc2portmap | |
127 | 369/udp rpc2portmap | |
128 | 370/tcp codaauth2 | |
129 | 370/udp codaauth2 | |
130 | 371/tcp clearcase | |
131 | 371/udp clearcase | |
132 | 372/tcp ulistserv | |
133 | 372/udp ulistserv | |
134 | 389/tcp ldap | |
135 | 389/udp ldap | |
136 | 406/tcp imsp | |
137 | 406/udp imsp | |
138 | 427/tcp svrloc | |
139 | 427/udp svrloc | |
140 | 443/tcp https | |
141 | 443/udp https | |
142 | 444/tcp snpp | |
143 | 444/udp snpp | |
144 | 445/tcp microsoft-ds | |
145 | 445/udp microsoft-ds | |
146 | 464/tcp kpasswd | |
147 | 464/udp kpasswd | |
148 | 465/tcp urd | |
149 | 487/tcp saft | |
150 | 487/udp saft | |
151 | 500/tcp isakmp | |
152 | 500/udp isakmp | |
153 | 554/tcp rtsp | |
154 | 554/udp rtsp | |
155 | 607/tcp nqs | |
156 | 607/udp nqs | |
157 | 610/tcp npmp-local | |
158 | 610/udp npmp-local | |
159 | 611/tcp npmp-gui | |
160 | 611/udp npmp-gui | |
161 | 612/tcp hmmp-ind | |
162 | 612/udp hmmp-ind | |
163 | 623/udp asf-rmcp | |
164 | 628/tcp qmqp | |
165 | 628/udp qmqp | |
166 | 631/tcp ipp | |
167 | 631/udp ipp | |
168 | 512/tcp exec | |
169 | 512/udp biff | |
170 | 513/tcp login | |
171 | 513/udp who | |
172 | 514/tcp shell | |
173 | 514/udp syslog | |
174 | 515/tcp printer | |
175 | 517/udp talk | |
176 | 518/udp ntalk | |
177 | 520/udp route | |
178 | 525/udp timed | |
179 | 526/tcp tempo | |
180 | 530/tcp courier | |
181 | 531/tcp conference | |
182 | 532/tcp netnews | |
183 | 533/udp netwall | |
184 | 538/tcp gdomap | |
185 | 538/udp gdomap | |
186 | 540/tcp uucp | |
187 | 543/tcp klogin | |
188 | 544/tcp kshell | |
189 | 546/tcp dhcpv6-client | |
190 | 546/udp dhcpv6-client | |
191 | 547/tcp dhcpv6-server | |
192 | 547/udp dhcpv6-server | |
193 | 548/tcp afpovertcp | |
194 | 548/udp afpovertcp | |
195 | 549/tcp idfp | |
196 | 549/udp idfp | |
197 | 556/tcp remotefs | |
198 | 563/tcp nntps | |
199 | 563/udp nntps | |
200 | 587/tcp submission | |
201 | 587/udp submission | |
202 | 636/tcp ldaps | |
203 | 636/udp ldaps | |
204 | 655/tcp tinc | |
205 | 655/udp tinc | |
206 | 706/tcp silc | |
207 | 706/udp silc | |
208 | 749/tcp kerberos-adm | |
209 | 765/tcp webster | |
210 | 765/udp webster | |
211 | 873/tcp rsync | |
212 | 873/udp rsync | |
213 | 989/tcp ftps-data | |
214 | 990/tcp ftps | |
215 | 992/tcp telnets | |
216 | 992/udp telnets | |
217 | 993/tcp imaps | |
218 | 993/udp imaps | |
219 | 994/tcp ircs | |
220 | 994/udp ircs | |
221 | 995/tcp pop3s | |
222 | 995/udp pop3s | |
223 | 1080/tcp socks | |
224 | 1080/udp socks | |
225 | 1093/tcp proofd | |
226 | 1093/udp proofd | |
227 | 1094/tcp rootd | |
228 | 1094/udp rootd | |
229 | 1194/tcp openvpn | |
230 | 1194/udp openvpn | |
231 | 1099/tcp rmiregistry | |
232 | 1099/udp rmiregistry | |
233 | 1214/tcp kazaa | |
234 | 1214/udp kazaa | |
235 | 1241/tcp nessus | |
236 | 1241/udp nessus | |
237 | 1352/tcp lotusnote | |
238 | 1352/udp lotusnote | |
239 | 1433/tcp ms-sql-s | |
240 | 1433/udp ms-sql-s | |
241 | 1434/tcp ms-sql-m | |
242 | 1434/udp ms-sql-m | |
243 | 1524/tcp ingreslock | |
244 | 1524/udp ingreslock | |
245 | 1525/tcp prospero-np | |
246 | 1525/udp prospero-np | |
247 | 1645/tcp datametrics | |
248 | 1645/udp datametrics | |
249 | 1646/tcp sa-msg-port | |
250 | 1646/udp sa-msg-port | |
251 | 1649/tcp kermit | |
252 | 1649/udp kermit | |
253 | 1677/tcp groupwise | |
254 | 1677/udp groupwise | |
255 | 1701/tcp l2f | |
256 | 1701/udp l2f | |
257 | 1812/tcp radius | |
258 | 1812/udp radius | |
259 | 1813/tcp radius-acct | |
260 | 1813/udp radius-acct | |
261 | 1863/tcp msnp | |
262 | 1863/udp msnp | |
263 | 1957/tcp unix-status | |
264 | 1958/tcp log-server | |
265 | 1959/tcp remoteping | |
266 | 2000/tcp cisco-sccp | |
267 | 2000/udp cisco-sccp | |
268 | 2010/tcp search | |
269 | 2010/tcp pipe-server | |
270 | 2049/tcp nfs | |
271 | 2049/udp nfs | |
272 | 2086/tcp gnunet | |
273 | 2086/udp gnunet | |
274 | 2101/tcp rtcm-sc104 | |
275 | 2101/udp rtcm-sc104 | |
276 | 2119/tcp gsigatekeeper | |
277 | 2119/udp gsigatekeeper | |
278 | 2135/tcp gris | |
279 | 2135/udp gris | |
280 | 2401/tcp cvspserver | |
281 | 2401/udp cvspserver | |
282 | 2430/tcp venus | |
283 | 2430/udp venus | |
284 | 2431/tcp venus-se | |
285 | 2431/udp venus-se | |
286 | 2432/tcp codasrv | |
287 | 2432/udp codasrv | |
288 | 2433/tcp codasrv-se | |
289 | 2433/udp codasrv-se | |
290 | 2583/tcp mon | |
291 | 2583/udp mon | |
292 | 2628/tcp dict | |
293 | 2628/udp dict | |
294 | 2792/tcp f5-globalsite | |
295 | 2792/udp f5-globalsite | |
296 | 2811/tcp gsiftp | |
297 | 2811/udp gsiftp | |
298 | 2947/tcp gpsd | |
299 | 2947/udp gpsd | |
300 | 3050/tcp gds-db | |
301 | 3050/udp gds-db | |
302 | 3130/tcp icpv2 | |
303 | 3130/udp icpv2 | |
304 | 3260/tcp iscsi-target | |
305 | 3306/tcp mysql | |
306 | 3306/udp mysql | |
307 | 3493/tcp nut | |
308 | 3493/udp nut | |
309 | 3632/tcp distcc | |
310 | 3632/udp distcc | |
311 | 3689/tcp daap | |
312 | 3689/udp daap | |
313 | 3690/tcp svn | |
314 | 3690/udp svn | |
315 | 4031/tcp suucp | |
316 | 4031/udp suucp | |
317 | 4094/tcp sysrqd | |
318 | 4094/udp sysrqd | |
319 | 4190/tcp sieve | |
320 | 4369/tcp epmd | |
321 | 4369/udp epmd | |
322 | 4373/tcp remctl | |
323 | 4373/udp remctl | |
324 | 4353/tcp f5-iquery | |
325 | 4353/udp f5-iquery | |
326 | 4500/udp ipsec-nat-t | |
327 | 4569/tcp iax | |
328 | 4569/udp iax | |
329 | 4691/tcp mtn | |
330 | 4691/udp mtn | |
331 | 4899/tcp radmin-port | |
332 | 4899/udp radmin-port | |
333 | 5002/udp rfe | |
334 | 5002/tcp rfe | |
335 | 5050/tcp mmcc | |
336 | 5050/udp mmcc | |
337 | 5060/tcp sip | |
338 | 5060/udp sip | |
339 | 5061/tcp sip-tls | |
340 | 5061/udp sip-tls | |
341 | 5190/tcp aol | |
342 | 5190/udp aol | |
343 | 5222/tcp xmpp-client | |
344 | 5222/udp xmpp-client | |
345 | 5269/tcp xmpp-server | |
346 | 5269/udp xmpp-server | |
347 | 5308/tcp cfengine | |
348 | 5308/udp cfengine | |
349 | 5353/tcp mdns | |
350 | 5353/udp mdns | |
351 | 5432/tcp postgresql | |
352 | 5432/udp postgresql | |
353 | 5556/tcp freeciv | |
354 | 5556/udp freeciv | |
355 | 5671/tcp amqps | |
356 | 5672/tcp amqp | |
357 | 5672/udp amqp | |
358 | 5672/sctp amqp | |
359 | 5688/tcp ggz | |
360 | 5688/udp ggz | |
361 | 6000/tcp x11 | |
362 | 6000/udp x11 | |
363 | 6001/tcp x11-1 | |
364 | 6001/udp x11-1 | |
365 | 6002/tcp x11-2 | |
366 | 6002/udp x11-2 | |
367 | 6003/tcp x11-3 | |
368 | 6003/udp x11-3 | |
369 | 6004/tcp x11-4 | |
370 | 6004/udp x11-4 | |
371 | 6005/tcp x11-5 | |
372 | 6005/udp x11-5 | |
373 | 6006/tcp x11-6 | |
374 | 6006/udp x11-6 | |
375 | 6007/tcp x11-7 | |
376 | 6007/udp x11-7 | |
377 | 6346/tcp gnutella-svc | |
378 | 6346/udp gnutella-svc | |
379 | 6347/tcp gnutella-rtr | |
380 | 6347/udp gnutella-rtr | |
381 | 6444/tcp sge-qmaster | |
382 | 6444/udp sge-qmaster | |
383 | 6445/tcp sge-execd | |
384 | 6445/udp sge-execd | |
385 | 6446/tcp mysql-proxy | |
386 | 6446/udp mysql-proxy | |
387 | 7000/tcp afs3-fileserver | |
388 | 7000/udp afs3-fileserver | |
389 | 7001/tcp afs3-callback | |
390 | 7001/udp afs3-callback | |
391 | 7002/tcp afs3-prserver | |
392 | 7002/udp afs3-prserver | |
393 | 7003/tcp afs3-vlserver | |
394 | 7003/udp afs3-vlserver | |
395 | 7004/tcp afs3-kaserver | |
396 | 7004/udp afs3-kaserver | |
397 | 7005/tcp afs3-volser | |
398 | 7005/udp afs3-volser | |
399 | 7006/tcp afs3-errors | |
400 | 7006/udp afs3-errors | |
401 | 7007/tcp afs3-bos | |
402 | 7007/udp afs3-bos | |
403 | 7008/tcp afs3-update | |
404 | 7008/udp afs3-update | |
405 | 7009/tcp afs3-rmtsys | |
406 | 7009/udp afs3-rmtsys | |
407 | 7100/tcp font-service | |
408 | 7100/udp font-service | |
409 | 8080/tcp http-alt | |
410 | 8080/udp http-alt | |
411 | 9101/tcp bacula-dir | |
412 | 9101/udp bacula-dir | |
413 | 9102/tcp bacula-fd | |
414 | 9102/udp bacula-fd | |
415 | 9103/tcp bacula-sd | |
416 | 9103/udp bacula-sd | |
417 | 9667/tcp xmms2 | |
418 | 9667/udp xmms2 | |
419 | 10809/tcp nbd | |
420 | 10050/tcp zabbix-agent | |
421 | 10050/udp zabbix-agent | |
422 | 10051/tcp zabbix-trapper | |
423 | 10051/udp zabbix-trapper | |
424 | 10080/tcp amanda | |
425 | 10080/udp amanda | |
426 | 11112/tcp dicom | |
427 | 11371/tcp hkp | |
428 | 11371/udp hkp | |
429 | 13720/tcp bprd | |
430 | 13720/udp bprd | |
431 | 13721/tcp bpdbm | |
432 | 13721/udp bpdbm | |
433 | 13722/tcp bpjava-msvc | |
434 | 13722/udp bpjava-msvc | |
435 | 13724/tcp vnetd | |
436 | 13724/udp vnetd | |
437 | 13782/tcp bpcd | |
438 | 13782/udp bpcd | |
439 | 13783/tcp vopied | |
440 | 13783/udp vopied | |
441 | 17500/tcp db-lsp | |
442 | 22125/tcp dcap | |
443 | 22128/tcp gsidcap | |
444 | 22273/tcp wnn6 | |
445 | 22273/udp wnn6 | |
446 | 1/ddp rtmp | |
447 | 2/ddp nbp | |
448 | 4/ddp echo | |
449 | 6/ddp zip | |
450 | 750/udp kerberos4 | |
451 | 750/tcp kerberos4 | |
452 | 751/udp kerberos-master | |
453 | 751/tcp kerberos-master | |
454 | 752/udp passwd-server | |
455 | 754/tcp krb-prop | |
456 | 760/tcp krbupdate | |
457 | 901/tcp swat | |
458 | 1109/tcp kpop | |
459 | 2053/tcp knetd | |
460 | 2102/udp zephyr-srv | |
461 | 2103/udp zephyr-clt | |
462 | 2104/udp zephyr-hm | |
463 | 2105/tcp eklogin | |
464 | 2111/tcp kx | |
465 | 2121/tcp iprop | |
466 | 871/tcp supfilesrv | |
467 | 1127/tcp supfiledbg | |
468 | 98/tcp linuxconf | |
469 | 106/tcp poppassd | |
470 | 106/udp poppassd | |
471 | 775/tcp moira-db | |
472 | 777/tcp moira-update | |
473 | 779/udp moira-ureg | |
474 | 783/tcp spamd | |
475 | 808/tcp omirr | |
476 | 808/udp omirr | |
477 | 1001/tcp customs | |
478 | 1001/udp customs | |
479 | 1178/tcp skkserv | |
480 | 1210/udp predict | |
481 | 1236/tcp rmtcfg | |
482 | 1300/tcp wipld | |
483 | 1313/tcp xtel | |
484 | 1314/tcp xtelw | |
485 | 1529/tcp support | |
486 | 2003/tcp cfinger | |
487 | 2121/tcp frox | |
488 | 2150/tcp ninstall | |
489 | 2150/udp ninstall | |
490 | 2600/tcp zebrasrv | |
491 | 2601/tcp zebra | |
492 | 2602/tcp ripd | |
493 | 2603/tcp ripngd | |
494 | 2604/tcp ospfd | |
495 | 2605/tcp bgpd | |
496 | 2606/tcp ospf6d | |
497 | 2607/tcp ospfapi | |
498 | 2608/tcp isisd | |
499 | 2988/tcp afbackup | |
500 | 2988/udp afbackup | |
501 | 2989/tcp afmbackup | |
502 | 2989/udp afmbackup | |
503 | 4224/tcp xtell | |
504 | 4557/tcp fax | |
505 | 4559/tcp hylafax | |
506 | 4600/tcp distmp3 | |
507 | 4949/tcp munin | |
508 | 5051/tcp enbd-cstatd | |
509 | 5052/tcp enbd-sstatd | |
510 | 5151/tcp pcrd | |
511 | 5354/tcp noclog | |
512 | 5354/udp noclog | |
513 | 5355/tcp hostmon | |
514 | 5355/udp hostmon | |
515 | 5555/udp rplay | |
516 | 5666/tcp nrpe | |
517 | 5667/tcp nsca | |
518 | 5674/tcp mrtd | |
519 | 5675/tcp bgpsim | |
520 | 5680/tcp canna | |
521 | 6514/tcp syslog-tls | |
522 | 6566/tcp sane-port | |
523 | 6667/tcp ircd | |
524 | 8021/tcp zope-ftp | |
525 | 8081/tcp tproxy | |
526 | 8088/tcp omniorb | |
527 | 8088/udp omniorb | |
528 | 8990/tcp clc-build-daemon | |
529 | 9098/tcp xinetd | |
530 | 9359/udp mandelspawn | |
531 | 9418/tcp git | |
532 | 9673/tcp zope | |
533 | 10000/tcp webmin | |
534 | 10081/tcp kamanda | |
535 | 10081/udp kamanda | |
536 | 10082/tcp amandaidx | |
537 | 10083/tcp amidxtape | |
538 | 11201/tcp smsqp | |
539 | 11201/udp smsqp | |
540 | 15345/tcp xpilot | |
541 | 15345/udp xpilot | |
542 | 17001/udp sgi-cmsd | |
543 | 17002/udp sgi-crsd | |
544 | 17003/udp sgi-gcd | |
545 | 17004/tcp sgi-cad | |
546 | 20011/tcp isdnlog | |
547 | 20011/udp isdnlog | |
548 | 20012/tcp vboxd | |
549 | 20012/udp vboxd | |
550 | 24554/tcp binkp | |
551 | 27374/tcp asp | |
552 | 27374/udp asp | |
553 | 30865/tcp csync2 | |
554 | 57000/tcp dircproxy | |
555 | 60177/tcp tfido | |
556 | 60179/tcp fido | |
557 | 1027/udp Native IPv6 behind IPv4-to-IPv4 NAT Customer Premises Equipment (6a44) | |
558 | 1058/tcp nim, IBM AIX Network Installation Manager (NIM) | |
559 | 1058/udp nim, IBM AIX Network Installation Manager (NIM) | |
560 | 1059/tcp nimreg, IBM AIX Network Installation Manager (NIM) | |
561 | 1059/udp nimreg, IBM AIX Network Installation Manager (NIM) | |
562 | 1080/tcp SOCKS proxy | |
563 | 1080/udp SOCKS proxy | |
564 | 1085/tcp WebObjects | |
565 | 1085/udp WebObjects | |
566 | 1098/tcp rmiactivation, Java remote method invocation (RMI) activation | |
567 | 1098/udp rmiactivation, Java remote method invocation (RMI) activation | |
568 | 1099/tcp rmiregistry, Java remote method invocation (RMI) registry | |
569 | 1099/assigned rmiregistry, Java remote method invocation (RMI) registry | |
570 | 1119/tcp Battle.net chat/game protocol, used by Blizzard's games | |
571 | 1119/udp Battle.net chat/game protocol, used by Blizzard's games | |
572 | 1167/udp Cisco IP SLA (Service Assurance Agent) | |
573 | 1167/tcp Cisco IP SLA (Service Assurance Agent) | |
574 | 1194/udp OpenVPN | |
575 | 1194/tcp OpenVPN | |
576 | 1198/udp The cajo project Free dynamic transparent distributed computing in Java | |
577 | 1198/tcp The cajo project Free dynamic transparent distributed computing in Java | |
578 | 1214/udp Kazaa | |
579 | 1214/tcp Kazaa | |
580 | 1234/udp Infoseek search agent | |
581 | 1234/tcp Infoseek search agent | |
582 | 1241/udp Nessus Security Scanner | |
583 | 1241/tcp Nessus Security Scanner | |
584 | 1270/udp Microsoft System Center Operations Manager (SCOM) (formerly Microsoft Operations Manager (MOM)) agent | |
585 | 1270/tcp Microsoft System Center Operations Manager (SCOM) (formerly Microsoft Operations Manager (MOM)) agent | |
586 | 1293/udp Internet Protocol Security (IPSec) | |
587 | 1293/tcp Internet Protocol Security (IPSec) | |
588 | 1311/udp Windows RxMon.exe | |
589 | 1311/tcp Windows RxMon.exe | |
590 | 1341/udp Qubes (Manufacturing Execution System) | |
591 | 1341/tcp Qubes (Manufacturing Execution System) | |
592 | 1344/udp Internet Content Adaptation Protocol | |
593 | 1344/tcp Internet Content Adaptation Protocol | |
594 | 1352/udp IBM Lotus Notes/Domino (RPC) protocol | |
595 | 1352/tcp IBM Lotus Notes/Domino (RPC) protocol | |
596 | 1360/udp Mimer SQL | |
597 | 1360/tcp Mimer SQL | |
598 | 1414/udp IBM WebSphere MQ (formerly known as MQSeries) | |
599 | 1414/tcp IBM WebSphere MQ (formerly known as MQSeries) | |
600 | 1417/udp Timbuktu Service 1 Port | |
601 | 1417/tcp Timbuktu Service 1 Port | |
602 | 1418/udp Timbuktu Service 2 Port | |
603 | 1418/tcp Timbuktu Service 2 Port | |
604 | 1419/udp Timbuktu Service 3 Port | |
605 | 1419/tcp Timbuktu Service 3 Port | |
606 | 1420/udp Timbuktu Service 4 Port | |
607 | 1420/tcp Timbuktu Service 4 Port | |
608 | 1433/udp Microsoft SQL Server database management system (MSSQL) server | |
609 | 1433/tcp Microsoft SQL Server database management system (MSSQL) server | |
610 | 1434/udp Microsoft SQL Server database management system (MSSQL) monitor | |
611 | 1434/tcp Microsoft SQL Server database management system (MSSQL) monitor | |
612 | 1512/udp Microsoft's Windows Internet Name Service (WINS) | |
613 | 1512/tcp Microsoft's Windows Internet Name Service (WINS) | |
614 | 1521/udp nCUBE License Manager | |
615 | 1521/tcp nCUBE License Manager | |
616 | 1524/udp ingreslock, ingres | |
617 | 1524/tcp ingreslock, ingres | |
618 | 1527/udp Oracle Net Services, formerly known as SQL*Net | |
619 | 1527/tcp Oracle Net Services, formerly known as SQL*Net | |
620 | 1533/udp IBM Sametime Virtual Places Chat | |
621 | 1533/tcp IBM Sametime Virtual Places Chat | |
622 | 1547/udp Laplink | |
623 | 1547/tcp Laplink | |
624 | 1581/udp MIL STD 2045-47001 VMF | |
625 | 1581/tcp MIL STD 2045-47001 VMF | |
626 | 1589/udp Cisco VLAN Query Protocol (VQP) | |
627 | 1589/tcp Cisco VLAN Query Protocol (VQP) | |
628 | 1628/udp LonTalk normal | |
629 | 1628/tcp LonTalk normal | |
630 | 1629/udp LonTalk urgent | |
631 | 1629/tcp LonTalk urgent | |
632 | 1677/udp Novell GroupWise clients in client/server access mode | |
633 | 1677/tcp Novell GroupWise clients in client/server access mode | |
634 | 1701/udp Layer 2 Forwarding Protocol (L2F) | |
635 | 1701/tcp Layer 2 Forwarding Protocol (L2F) | |
636 | 1701/udp Layer 2 Tunneling Protocol (L2TP) | |
637 | 1701/assigned Layer 2 Tunneling Protocol (L2TP) | |
638 | 1707/udp Windward Studios games (vdmplay) | |
639 | 1707/tcp Windward Studios games (vdmplay) | |
640 | 1719/udp H.323 registration and alternate communication | |
641 | 1719/tcp H.323 registration and alternate communication | |
642 | 1720/udp H.323 call signaling | |
643 | 1720/tcp H.323 call signaling | |
644 | 1755/udp Microsoft Media Services (MMS, ms-streaming) | |
645 | 1755/tcp Microsoft Media Services (MMS, ms-streaming) | |
646 | 1801/udp Microsoft Message Queuing | |
647 | 1801/tcp Microsoft Message Queuing | |
648 | 1812/udp RADIUS authentication protocol, radius | |
649 | 1812/tcp RADIUS authentication protocol, radius | |
650 | 1813/udp RADIUS accounting protocol, radius-acct | |
651 | 1813/tcp RADIUS accounting protocol, radius-acct | |
652 | 1863/udp Microsoft Notification Protocol (MSNP), used by the Microsoft Messenger service and a number of instant messaging Messenger clients | |
653 | 1863/tcp Microsoft Notification Protocol (MSNP), used by the Microsoft Messenger service and a number of instant messaging Messenger clients | |
654 | 1883/udp MQTT (formerly MQ Telemetry Transport) | |
655 | 1883/tcp MQTT (formerly MQ Telemetry Transport) | |
656 | 1900/udp Simple Service Discovery Protocol (SSDP), discovery of UPnP devices | |
657 | 1900/assigned Simple Service Discovery Protocol (SSDP), discovery of UPnP devices | |
658 | 1935/udp Macromedia Flash Communications Server MX, the precursor to Adobe Flash Media Server before Macromedia's acquisition by Adobe on December 3, 2005 | |
659 | 1935/tcp Macromedia Flash Communications Server MX, the precursor to Adobe Flash Media Server before Macromedia's acquisition by Adobe on December 3, 2005 | |
660 | 1970/udp Netop Remote Control | |
661 | 1970/tcp Netop Remote Control | |
662 | 1972/udp InterSystems Cache | |
663 | 1972/tcp InterSystems Cache | |
664 | 1984/udp Big Brother | |
665 | 1984/tcp Big Brother | |
666 | 1985/udp Cisco Hot Standby Router Protocol (HSRP) | |
667 | 1985/assigned Cisco Hot Standby Router Protocol (HSRP) | |
668 | 1998/udp Cisco X.25 over TCP (XOT) service | |
669 | 1998/tcp Cisco X.25 over TCP (XOT) service | |
670 | 2000/udp Cisco Skinny Client Control Protocol (SCCP) | |
671 | 2000/tcp Cisco Skinny Client Control Protocol (SCCP) | |
672 | 2080/udp Autodesk NLM (FLEXlm) | |
673 | 2080/tcp Autodesk NLM (FLEXlm) | |
674 | 2083/udp Secure RADIUS Service (radsec) | |
675 | 2083/tcp Secure RADIUS Service (radsec) | |
676 | 2086/udp GNUnet | |
677 | 2086/tcp GNUnet | |
678 | 2102/udp Zephyr Notification Service server | |
679 | 2102/tcp Zephyr Notification Service server | |
680 | 2103/udp Zephyr Notification Service serv-hm connection | |
681 | 2103/tcp Zephyr Notification Service serv-hm connection | |
682 | 2104/udp Zephyr Notification Service hostmanager | |
683 | 2104/tcp Zephyr Notification Service hostmanager | |
684 | 2123/udp GTP control messages (GTP-C) | |
685 | 2123/tcp GTP control messages (GTP-C) | |
686 | 2142/udp TDMoIP (TDM over IP) | |
687 | 2142/tcp TDMoIP (TDM over IP) | |
688 | 2152/udp GTP user data messages (GTP-U) | |
689 | 2152/tcp GTP user data messages (GTP-U) | |
690 | 2159/udp GDB remote debug port | |
691 | 2159/tcp GDB remote debug port | |
692 | 2181/udp EForward-document transport system | |
693 | 2181/tcp EForward-document transport system | |
694 | 2210/udp NOAAPORT Broadcast Network | |
695 | 2210/tcp NOAAPORT Broadcast Network | |
696 | 2211/udp EMWIN | |
697 | 2211/tcp EMWIN | |
698 | 2222/udp EtherNet/IP implicit messaging for IO data | |
699 | 2222/tcp EtherNet/IP implicit messaging for IO data | |
700 | 2261/udp CoMotion master | |
701 | 2261/tcp CoMotion master | |
702 | 2262/udp CoMotion backup | |
703 | 2262/tcp CoMotion backup | |
704 | 2266/udp M-Files | |
705 | 2266/tcp M-Files | |
706 | 2399/udp FileMaker Data Access Layer (ODBC/JDBC) | |
707 | 2399/tcp FileMaker Data Access Layer (ODBC/JDBC) | |
708 | 2401/udp CVS version control system password-based server | |
709 | 2401/tcp CVS version control system password-based server | |
710 | 2404/udp IEC 60870-5-104, used to send electric power telecontrol messages between two systems via directly connected data circuits | |
711 | 2404/tcp IEC 60870-5-104, used to send electric power telecontrol messages between two systems via directly connected data circuits | |
712 | 2427/udp Media Gateway Control Protocol (MGCP) media gateway | |
713 | 2427/tcp Media Gateway Control Protocol (MGCP) media gateway | |
714 | 2447/udp ovwdb OpenView Network Node Manager (NNM) daemon | |
715 | 2447/tcp ovwdb OpenView Network Node Manager (NNM) daemon | |
716 | 2483/udp Oracle database listening for insecure client connections to the listener, replaces port 1521 | |
717 | 2483/tcp Oracle database listening for insecure client connections to the listener, replaces port 1521 | |
718 | 2484/udp Oracle database listening for SSL client connections to the listener | |
719 | 2484/tcp Oracle database listening for SSL client connections to the listener | |
720 | 2535/udp Multicast Address Dynamic Client Allocation Protocol (MADCAP). All standard messages are UDP datagrams. | |
721 | 2535/tcp Multicast Address Dynamic Client Allocation Protocol (MADCAP). All standard messages are UDP datagrams. | |
722 | 2541/udp LonTalk/IP | |
723 | 2541/tcp LonTalk/IP | |
724 | 2546/udp EVault data protection services | |
725 | 2546/tcp EVault data protection services | |
726 | 2547/udp EVault data protection services | |
727 | 2547/tcp EVault data protection services | |
728 | 2548/udp EVault data protection services | |
729 | 2548/tcp EVault data protection services | |
730 | 2638/udp SQL Anywhere database server | |
731 | 2638/tcp SQL Anywhere database server | |
732 | 2727/udp Media Gateway Control Protocol (MGCP) media gateway controller (call agent) | |
733 | 2727/tcp Media Gateway Control Protocol (MGCP) media gateway controller (call agent) | |
734 | 2809/udp corbaloc:iiop URL, per the CORBA 3.0.3 specification | |
735 | 2809/tcp corbaloc:iiop URL, per the CORBA 3.0.3 specification | |
736 | 2811/udp gsi ftp, per the GridFTP specification | |
737 | 2811/tcp gsi ftp, per the GridFTP specification | |
738 | 2944/udp Megaco text H.248 | |
739 | 2944/tcp Megaco text H.248 | |
740 | 2945/udp Megaco binary (ASN.1) H.248 | |
741 | 2945/tcp Megaco binary (ASN.1) H.248 | |
742 | 2947/udp gpsd, GPS daemon | |
743 | 2947/tcp gpsd, GPS daemon | |
744 | 2948/udp WAP push Multimedia Messaging Service (MMS) | |
745 | 2948/tcp WAP push Multimedia Messaging Service (MMS) | |
746 | 2949/udp WAP push secure (MMS) | |
747 | 2949/tcp WAP push secure (MMS) | |
748 | 2967/udp Symantec System Center agent (SSC-AGENT) | |
749 | 2967/tcp Symantec System Center agent (SSC-AGENT) | |
750 | 3020/udp Common Internet File System (CIFS). See also port 445 for Server Message Block (SMB), a dialect of CIFS. | |
751 | 3020/tcp Common Internet File System (CIFS). See also port 445 for Server Message Block (SMB), a dialect of CIFS. | |
752 | 3050/udp gds-db (Interbase/Firebird databases) | |
753 | 3050/tcp gds-db (Interbase/Firebird databases) | |
754 | 3052/udp APC PowerChute Network | |
755 | 3052/tcp APC PowerChute Network | |
756 | 3074/udp Xbox LIVE and Games for Windows Live | |
757 | 3074/tcp Xbox LIVE and Games for Windows Live | |
758 | 3225/udp Fibre Channel over IP (FCIP) | |
759 | 3225/tcp Fibre Channel over IP (FCIP) | |
760 | 3233/udp WhiskerControl research control protocol | |
761 | 3233/tcp WhiskerControl research control protocol | |
762 | 3260/udp iSCSI | |
763 | 3260/tcp iSCSI | |
764 | 3268/udp msft-gc, Microsoft Global Catalog (LDAP service which contains data from Active Directory forests) | |
765 | 3268/tcp msft-gc, Microsoft Global Catalog (LDAP service which contains data from Active Directory forests) | |
766 | 3269/udp msft-gc-ssl, Microsoft Global Catalog over SSL (similar to port 3268, LDAP over SSL) | |
767 | 3269/tcp msft-gc-ssl, Microsoft Global Catalog over SSL (similar to port 3268, LDAP over SSL) | |
768 | 3283/udp Net Assistant, a predecessor to Apple Remote Desktop | |
769 | 3283/tcp Net Assistant, a predecessor to Apple Remote Desktop | |
770 | 3305/udp Odette File Transfer Protocol (OFTP) | |
771 | 3305/tcp Odette File Transfer Protocol (OFTP) | |
772 | 3386/udp GTP' 3GPP GSM/UMTS CDR logging protocol | |
773 | 3386/tcp GTP' 3GPP GSM/UMTS CDR logging protocol | |
774 | 3389/udp Microsoft Terminal Server (RDP) officially registered as Windows Based Terminal (WBT) | |
775 | 3389/tcp Microsoft Terminal Server (RDP) officially registered as Windows Based Terminal (WBT) | |
776 | 3396/udp Novell NDPS Printer Agent | |
777 | 3396/tcp Novell NDPS Printer Agent | |
778 | 3412/udp xmlBlaster | |
779 | 3412/tcp xmlBlaster | |
780 | 3455/udp Resource Reservation Protocol (RSVP) | |
781 | 3455/tcp Resource Reservation Protocol (RSVP) | |
782 | 3478/udp STUN, a protocol for NAT traversal | |
783 | 3478/tcp STUN, a protocol for NAT traversal | |
784 | 3478/udp TURN, a protocol for NAT traversal (extension to STUN) | |
785 | 3478/tcp TURN, a protocol for NAT traversal (extension to STUN) | |
786 | 3478/udp STUN Behavior Discovery. See also port 5349. | |
787 | 3478/tcp STUN Behavior Discovery. See also port 5349. | |
788 | 3493/udp Network UPS Tools (NUT) | |
789 | 3493/tcp Network UPS Tools (NUT) | |
790 | 3516/udp Smartcard Port | |
791 | 3516/tcp Smartcard Port | |
792 | 3645/udp Cyc | |
793 | 3645/tcp Cyc | |
794 | 3659/udp Apple SASL, used by Mac OS X Server Password Server | |
795 | 3659/tcp Apple SASL, used by Mac OS X Server Password Server | |
796 | 3667/udp Information Exchange | |
797 | 3667/tcp Information Exchange | |
798 | 3690/udp Subversion (SVN) version control system | |
799 | 3690/tcp Subversion (SVN) version control system | |
800 | 3702/udp Web Services Dynamic Discovery (WS-Discovery), used by various components of Windows Vista and later | |
801 | 3702/tcp Web Services Dynamic Discovery (WS-Discovery), used by various components of Windows Vista and later | |
802 | 3724/udp Some Blizzard games | |
803 | 3724/tcp Some Blizzard games | |
804 | 3725/udp Netia NA-ER Port | |
805 | 3725/tcp Netia NA-ER Port | |
806 | 3768/udp RBLcheckd server daemon | |
807 | 3768/tcp RBLcheckd server daemon | |
808 | 3804/udp Harman Professional HiQnet protocol | |
809 | 3804/tcp Harman Professional HiQnet protocol | |
810 | 3826/udp WarMUX game server | |
811 | 3826/tcp WarMUX game server | |
812 | 3830/udp System Management Agent, developed and used by Cerner to monitor and manage solutions | |
813 | 3830/tcp System Management Agent, developed and used by Cerner to monitor and manage solutions | |
814 | 3880/udp IGRS | |
815 | 3880/tcp IGRS | |
816 | 3999/udp Norman distributed scanning service | |
817 | 3999/tcp Norman distributed scanning service | |
818 | 4018/udp Protocol information and warnings | |
819 | 4018/tcp Protocol information and warnings | |
820 | 4089/udp OpenCORE Remote Control Service | |
821 | 4089/tcp OpenCORE Remote Control Service | |
822 | 4090/udp Kerio | |
823 | 4090/tcp Kerio | |
824 | 4093/udp PxPlus Client server interface ProvideX | |
825 | 4093/tcp PxPlus Client server interface ProvideX | |
826 | 4096/udp Ascom Timeplex Bridge Relay Element (BRE) | |
827 | 4096/tcp Ascom Timeplex Bridge Relay Element (BRE) | |
828 | 4105/udp Shofar (ShofarNexus) | |
829 | 4105/tcp Shofar (ShofarNexus) | |
830 | 4116/udp Smartcard-TLS | |
831 | 4116/tcp Smartcard-TLS | |
832 | 4172/udp Teradici PCoIP | |
833 | 4172/tcp Teradici PCoIP | |
834 | 4303/udp Simple Railroad Command Protocol (SRCP) | |
835 | 4303/tcp Simple Railroad Command Protocol (SRCP) | |
836 | 4486/udp Integrated Client Message Service (ICMS) | |
837 | 4486/tcp Integrated Client Message Service (ICMS) | |
838 | 4500/udp IPSec NAT Traversal (RFC 3947, RFC 4306) | |
839 | 4500/assigned IPSec NAT Traversal (RFC 3947, RFC 4306) | |
840 | 4662/udp OrbitNet Message Service | |
841 | 4662/tcp OrbitNet Message Service | |
842 | 4730/udp Gearman's job server | |
843 | 4730/tcp Gearman's job server | |
844 | 4739/udp IP Flow Information Export | |
845 | 4739/tcp IP Flow Information Export | |
846 | 4753/udp SIMON (service and discovery) | |
847 | 4753/tcp SIMON (service and discovery) | |
848 | 4840/udp OPC UA Connection Protocol (TCP) and OPC UA Multicast Datagram Protocol (UDP) for OPC Unified Architecture from OPC Foundation | |
849 | 4840/tcp OPC UA Connection Protocol (TCP) and OPC UA Multicast Datagram Protocol (UDP) for OPC Unified Architecture from OPC Foundation | |
850 | 4843/udp OPC UA TCP Protocol over TLS/SSL for OPC Unified Architecture from OPC Foundation | |
851 | 4843/tcp OPC UA TCP Protocol over TLS/SSL for OPC Unified Architecture from OPC Foundation | |
852 | 4847/udp Web Fresh Communication, Quadrion Software & Odorless Entertainment | |
853 | 4847/tcp Web Fresh Communication, Quadrion Software & Odorless Entertainment | |
854 | 4894/udp LysKOM Protocol A | |
855 | 4894/tcp LysKOM Protocol A | |
856 | 4950/udp Cylon Controls UC32 Communications Port | |
857 | 4950/tcp Cylon Controls UC32 Communications Port | |
858 | 5010/udp Registered to: TelePath (the IBM FlowMark workflow-management system messaging platform). The TCP port is now used for: IBM WebSphere MQ Workflow | |
859 | 5010/tcp Registered to: TelePath (the IBM FlowMark workflow-management system messaging platform). The TCP port is now used for: IBM WebSphere MQ Workflow | |
860 | 5011/udp TelePath (the IBM FlowMark workflow-management system messaging platform) | |
861 | 5011/tcp TelePath (the IBM FlowMark workflow-management system messaging platform) | |
862 | 5025/udp scpi-raw Standard Commands for Programmable Instruments | |
863 | 5025/tcp scpi-raw Standard Commands for Programmable Instruments | |
864 | 5060/udp Session Initiation Protocol (SIP) | |
865 | 5060/tcp Session Initiation Protocol (SIP) | |
866 | 5062/udp Localisation access | |
867 | 5062/tcp Localisation access | |
868 | 5064/udp EPICS Channel Access server | |
869 | 5064/tcp EPICS Channel Access server | |
870 | 5065/udp EPICS Channel Access repeater beacon | |
871 | 5065/tcp EPICS Channel Access repeater beacon | |
872 | 5084/udp EPCglobal Low Level Reader Protocol (LLRP) | |
873 | 5084/tcp EPCglobal Low Level Reader Protocol (LLRP) | |
874 | 5085/udp EPCglobal Low Level Reader Protocol (LLRP) over TLS | |
875 | 5085/tcp EPCglobal Low Level Reader Protocol (LLRP) over TLS | |
876 | 5099/udp SafeNet, Inc Sentinel LM, Sentinel RMS, License Manager, server-to-server | |
877 | 5099/tcp SafeNet, Inc Sentinel LM, Sentinel RMS, License Manager, server-to-server | |
878 | 5150/udp ATMP Ascend Tunnel Management Protocol | |
879 | 5150/tcp ATMP Ascend Tunnel Management Protocol | |
880 | 5154/udp BZFlag | |
881 | 5154/tcp BZFlag | |
882 | 5190/udp AOL Instant Messenger protocol. The chat app is defunct as of 15 December 2017. | |
883 | 5190/tcp AOL Instant Messenger protocol. The chat app is defunct as of 15 December 2017. | |
884 | 5298/udp Extensible Messaging and Presence Protocol (XMPP) | |
885 | 5298/tcp Extensible Messaging and Presence Protocol (XMPP) | |
886 | 5310/udp Outlaws (1997 video game). Both UDP and TCP are reserved, but only UDP is used | |
887 | 5310/tcp Outlaws (1997 video game). Both UDP and TCP are reserved, but only UDP is used | |
888 | 5353/udp Multicast DNS (mDNS) | |
889 | 5353/assigned Multicast DNS (mDNS) | |
890 | 5355/udp Link-Local Multicast Name Resolution (LLMNR), allows hosts to perform name resolution for hosts on the same local link (only provided by Windows Vista and Server 2008) | |
891 | 5355/tcp Link-Local Multicast Name Resolution (LLMNR), allows hosts to perform name resolution for hosts on the same local link (only provided by Windows Vista and Server 2008) | |
892 | 5402/udp Multicast File Transfer Protocol (MFTP) | |
893 | 5402/tcp Multicast File Transfer Protocol (MFTP) | |
894 | 5405/udp NetSupport Manager | |
895 | 5405/tcp NetSupport Manager | |
896 | 5412/udp IBM Rational Synergy (Telelogic Synergy) (Continuus CM) Message Router | |
897 | 5412/tcp IBM Rational Synergy (Telelogic Synergy) (Continuus CM) Message Router | |
898 | 5413/udp Wonderware SuiteLink service | |
899 | 5413/tcp Wonderware SuiteLink service | |
900 | 5417/udp SNS Agent | |
901 | 5417/tcp SNS Agent | |
902 | 5421/udp NetSupport Manager | |
903 | 5421/tcp NetSupport Manager | |
904 | 5556/udp Freeciv, Oracle WebLogic Server Node Manager | |
905 | 5556/tcp Freeciv, Oracle WebLogic Server Node Manager | |
906 | 5568/udp Session Data Transport (SDT), a part of Architecture for Control Networks (ACN) | |
907 | 5568/tcp Session Data Transport (SDT), a part of Architecture for Control Networks (ACN) | |
908 | 5722/udp Microsoft RPC, DFSR (SYSVOL) Replication Service | |
909 | 5722/tcp Microsoft RPC, DFSR (SYSVOL) Replication Service | |
910 | 5741/udp IDA Discover Port 1 | |
911 | 5741/tcp IDA Discover Port 1 | |
912 | 5742/udp IDA Discover Port 2 | |
913 | 5742/tcp IDA Discover Port 2 | |
914 | 5900/udp Remote Frame Buffer protocol (RFB) | |
915 | 5900/tcp Remote Frame Buffer protocol (RFB) | |
916 | 5931/udp AMMYY admin Remote Control | |
917 | 5931/tcp AMMYY admin Remote Control | |
918 | 5984/udp CouchDB database server | |
919 | 5984/tcp CouchDB database server | |
920 | 6000/udp X11-used between an X client and server over the network | |
921 | 6000/tcp X11-used between an X client and server over the network | |
922 | 6001/udp X11-used between an X client and server over the network | |
923 | 6001/tcp X11-used between an X client and server over the network | |
924 | 6002/udp X11-used between an X client and server over the network | |
925 | 6002/tcp X11-used between an X client and server over the network | |
926 | 6003/udp X11-used between an X client and server over the network | |
927 | 6003/tcp X11-used between an X client and server over the network | |
928 | 6004/udp X11-used between an X client and server over the network | |
929 | 6004/tcp X11-used between an X client and server over the network | |
930 | 6005/udp X11-used between an X client and server over the network | |
931 | 6005/tcp X11-used between an X client and server over the network | |
932 | 6006/udp X11-used between an X client and server over the network | |
933 | 6006/tcp X11-used between an X client and server over the network | |
934 | 6007/udp X11-used between an X client and server over the network | |
935 | 6007/tcp X11-used between an X client and server over the network | |
936 | 6008/udp X11-used between an X client and server over the network | |
937 | 6008/tcp X11-used between an X client and server over the network | |
938 | 6009/udp X11-used between an X client and server over the network | |
939 | 6009/tcp X11-used between an X client and server over the network | |
940 | 6010/udp X11-used between an X client and server over the network | |
941 | 6010/tcp X11-used between an X client and server over the network | |
942 | 6011/udp X11-used between an X client and server over the network | |
943 | 6011/tcp X11-used between an X client and server over the network | |
944 | 6012/udp X11-used between an X client and server over the network | |
945 | 6012/tcp X11-used between an X client and server over the network | |
946 | 6013/udp X11-used between an X client and server over the network | |
947 | 6013/tcp X11-used between an X client and server over the network | |
948 | 6014/udp X11-used between an X client and server over the network | |
949 | 6014/tcp X11-used between an X client and server over the network | |
950 | 6015/udp X11-used between an X client and server over the network | |
951 | 6015/tcp X11-used between an X client and server over the network | |
952 | 6016/udp X11-used between an X client and server over the network | |
953 | 6016/tcp X11-used between an X client and server over the network | |
954 | 6017/udp X11-used between an X client and server over the network | |
955 | 6017/tcp X11-used between an X client and server over the network | |
956 | 6018/udp X11-used between an X client and server over the network | |
957 | 6018/tcp X11-used between an X client and server over the network | |
958 | 6019/udp X11-used between an X client and server over the network | |
959 | 6019/tcp X11-used between an X client and server over the network | |
960 | 6020/udp X11-used between an X client and server over the network | |
961 | 6020/tcp X11-used between an X client and server over the network | |
962 | 6021/udp X11-used between an X client and server over the network | |
963 | 6021/tcp X11-used between an X client and server over the network | |
964 | 6022/udp X11-used between an X client and server over the network | |
965 | 6022/tcp X11-used between an X client and server over the network | |
966 | 6023/udp X11-used between an X client and server over the network | |
967 | 6023/tcp X11-used between an X client and server over the network | |
968 | 6024/udp X11-used between an X client and server over the network | |
969 | 6024/tcp X11-used between an X client and server over the network | |
970 | 6025/udp X11-used between an X client and server over the network | |
971 | 6025/tcp X11-used between an X client and server over the network | |
972 | 6026/udp X11-used between an X client and server over the network | |
973 | 6026/tcp X11-used between an X client and server over the network | |
974 | 6027/udp X11-used between an X client and server over the network | |
975 | 6027/tcp X11-used between an X client and server over the network | |
976 | 6028/udp X11-used between an X client and server over the network | |
977 | 6028/tcp X11-used between an X client and server over the network | |
978 | 6029/udp X11-used between an X client and server over the network | |
979 | 6029/tcp X11-used between an X client and server over the network | |
980 | 6030/udp X11-used between an X client and server over the network | |
981 | 6030/tcp X11-used between an X client and server over the network | |
982 | 6031/udp X11-used between an X client and server over the network | |
983 | 6031/tcp X11-used between an X client and server over the network | |
984 | 6032/udp X11-used between an X client and server over the network | |
985 | 6032/tcp X11-used between an X client and server over the network | |
986 | 6033/udp X11-used between an X client and server over the network | |
987 | 6033/tcp X11-used between an X client and server over the network | |
988 | 6034/udp X11-used between an X client and server over the network | |
989 | 6034/tcp X11-used between an X client and server over the network | |
990 | 6035/udp X11-used between an X client and server over the network | |
991 | 6035/tcp X11-used between an X client and server over the network | |
992 | 6036/udp X11-used between an X client and server over the network | |
993 | 6036/tcp X11-used between an X client and server over the network | |
994 | 6037/udp X11-used between an X client and server over the network | |
995 | 6037/tcp X11-used between an X client and server over the network | |
996 | 6038/udp X11-used between an X client and server over the network | |
997 | 6038/tcp X11-used between an X client and server over the network | |
998 | 6039/udp X11-used between an X client and server over the network | |
999 | 6039/tcp X11-used between an X client and server over the network | |
1000 | 6040/udp X11-used between an X client and server over the network | |
1001 | 6040/tcp X11-used between an X client and server over the network | |
1002 | 6041/udp X11-used between an X client and server over the network | |
1003 | 6041/tcp X11-used between an X client and server over the network | |
1004 | 6042/udp X11-used between an X client and server over the network | |
1005 | 6042/tcp X11-used between an X client and server over the network | |
1006 | 6043/udp X11-used between an X client and server over the network | |
1007 | 6043/tcp X11-used between an X client and server over the network | |
1008 | 6044/udp X11-used between an X client and server over the network | |
1009 | 6044/tcp X11-used between an X client and server over the network | |
1010 | 6045/udp X11-used between an X client and server over the network | |
1011 | 6045/tcp X11-used between an X client and server over the network | |
1012 | 6046/udp X11-used between an X client and server over the network | |
1013 | 6046/tcp X11-used between an X client and server over the network | |
1014 | 6047/udp X11-used between an X client and server over the network | |
1015 | 6047/tcp X11-used between an X client and server over the network | |
1016 | 6048/udp X11-used between an X client and server over the network | |
1017 | 6048/tcp X11-used between an X client and server over the network | |
1018 | 6049/udp X11-used between an X client and server over the network | |
1019 | 6049/tcp X11-used between an X client and server over the network | |
1020 | 6050/udp X11-used between an X client and server over the network | |
1021 | 6050/tcp X11-used between an X client and server over the network | |
1022 | 6051/udp X11-used between an X client and server over the network | |
1023 | 6051/tcp X11-used between an X client and server over the network | |
1024 | 6052/udp X11-used between an X client and server over the network | |
1025 | 6052/tcp X11-used between an X client and server over the network | |
1026 | 6053/udp X11-used between an X client and server over the network | |
1027 | 6053/tcp X11-used between an X client and server over the network | |
1028 | 6054/udp X11-used between an X client and server over the network | |
1029 | 6054/tcp X11-used between an X client and server over the network | |
1030 | 6055/udp X11-used between an X client and server over the network | |
1031 | 6055/tcp X11-used between an X client and server over the network | |
1032 | 6056/udp X11-used between an X client and server over the network | |
1033 | 6056/tcp X11-used between an X client and server over the network | |
1034 | 6057/udp X11-used between an X client and server over the network | |
1035 | 6057/tcp X11-used between an X client and server over the network | |
1036 | 6058/udp X11-used between an X client and server over the network | |
1037 | 6058/tcp X11-used between an X client and server over the network | |
1038 | 6059/udp X11-used between an X client and server over the network | |
1039 | 6059/tcp X11-used between an X client and server over the network | |
1040 | 6060/udp X11-used between an X client and server over the network | |
1041 | 6060/tcp X11-used between an X client and server over the network | |
1042 | 6061/udp X11-used between an X client and server over the network | |
1043 | 6061/tcp X11-used between an X client and server over the network | |
1044 | 6062/udp X11-used between an X client and server over the network | |
1045 | 6062/tcp X11-used between an X client and server over the network | |
1046 | 6063/udp X11-used between an X client and server over the network | |
1047 | 6063/tcp X11-used between an X client and server over the network | |
1048 | 6110/udp softcm, HP Softbench CM | |
1049 | 6110/tcp softcm, HP Softbench CM | |
1050 | 6111/udp spc, HP Softbench Sub-Process Control | |
1051 | 6111/tcp spc, HP Softbench Sub-Process Control | |
1052 | 6112/udp dtspcd, execute commands and launch applications remotely | |
1053 | 6112/tcp dtspcd, execute commands and launch applications remotely | |
1054 | 6346/udp gnutella-svc, gnutella (FrostWire, Limewire, Shareaza, etc.) | |
1055 | 6346/tcp gnutella-svc, gnutella (FrostWire, Limewire, Shareaza, etc.) | |
1056 | 6347/udp gnutella-rtr, Gnutella alternate | |
1057 | 6347/tcp gnutella-rtr, Gnutella alternate | |
1058 | 6350/udp App Discovery and Access Protocol | |
1059 | 6350/tcp App Discovery and Access Protocol | |
1060 | 6444/udp Sun Grid Engine Qmaster Service | |
1061 | 6444/tcp Sun Grid Engine Qmaster Service | |
1062 | 6445/udp Sun Grid Engine Execution Service | |
1063 | 6445/tcp Sun Grid Engine Execution Service | |
1064 | 6464/udp Port assignment for medical device communication in accordance to IEEE 11073-20701 | |
1065 | 6464/tcp Port assignment for medical device communication in accordance to IEEE 11073-20701 | |
1066 | 6515/udp Elipse RPC Protocol (REC) | |
1067 | 6515/tcp Elipse RPC Protocol (REC) | |
1068 | 6619/udp odette-ftps, Odette File Transfer Protocol (OFTP) over TLS/SSL | |
1069 | 6619/tcp odette-ftps, Odette File Transfer Protocol (OFTP) over TLS/SSL | |
1070 | 6622/udp Multicast FTP | |
1071 | 6622/tcp Multicast FTP | |
1072 | 6679/udp Osorno Automation Protocol (OSAUT) | |
1073 | 6679/tcp Osorno Automation Protocol (OSAUT) | |
1074 | 6888/udp MUSE | |
1075 | 6888/tcp MUSE | |
1076 | 6969/udp acmsoda | |
1077 | 6969/tcp acmsoda | |
1078 | 7262/udp CNAP (Calypso Network Access Protocol) | |
1079 | 7262/tcp CNAP (Calypso Network Access Protocol) | |
1080 | 7272/udp WatchMe - WatchMe Monitoring | |
1081 | 7272/tcp WatchMe - WatchMe Monitoring | |
1082 | 7400/udp RTPS (Real Time Publish Subscribe) DDS Discovery | |
1083 | 7400/tcp RTPS (Real Time Publish Subscribe) DDS Discovery | |
1084 | 7401/udp RTPS (Real Time Publish Subscribe) DDS User-Traffic | |
1085 | 7401/tcp RTPS (Real Time Publish Subscribe) DDS User-Traffic | |
1086 | 7402/udp RTPS (Real Time Publish Subscribe) DDS Meta-Traffic | |
1087 | 7402/tcp RTPS (Real Time Publish Subscribe) DDS Meta-Traffic | |
1088 | 7542/udp Saratoga file transfer protocol | |
1089 | 7542/tcp Saratoga file transfer protocol | |
1090 | 7547/udp CPE WAN Management Protocol (CWMP) Technical Report 069 | |
1091 | 7547/tcp CPE WAN Management Protocol (CWMP) Technical Report 069 | |
1092 | 7624/udp Instrument Neutral Distributed Interface | |
1093 | 7624/tcp Instrument Neutral Distributed Interface | |
1094 | 8008/udp Alternative port for HTTP. See also ports 80 and 8080. | |
1095 | 8008/tcp Alternative port for HTTP. See also ports 80 and 8080. | |
1096 | 8074/udp Gadu-Gadu | |
1097 | 8074/tcp Gadu-Gadu | |
1098 | 8080/udp Alternative port for HTTP. See also ports 80 and 8008. | |
1099 | 8080/tcp Alternative port for HTTP. See also ports 80 and 8008. | |
1100 | 8243/udp HTTPS listener for Apache Synapse | |
1101 | 8243/tcp HTTPS listener for Apache Synapse | |
1102 | 8280/udp HTTP listener for Apache Synapse | |
1103 | 8280/tcp HTTP listener for Apache Synapse | |
1104 | 8883/udp Secure MQTT (MQTT over TLS) | |
1105 | 8883/tcp Secure MQTT (MQTT over TLS) | |
1106 | 9001/udp ETL Service Manager | |
1107 | 9001/tcp ETL Service Manager | |
1108 | 9080/udp glrpc, Groove Collaboration software GLRPC | |
1109 | 9080/tcp glrpc, Groove Collaboration software GLRPC | |
1110 | 9101/udp Bacula Director | |
1111 | 9101/tcp Bacula Director | |
1112 | 9102/udp Bacula File Daemon | |
1113 | 9102/tcp Bacula File Daemon | |
1114 | 9103/udp Bacula Storage Daemon | |
1115 | 9103/tcp Bacula Storage Daemon | |
1116 | 9119/udp MXit Instant Messenger | |
1117 | 9119/tcp MXit Instant Messenger | |
1118 | 9389/udp adws, Microsoft AD DS Web Services, Powershell uses this port | |
1119 | 9389/tcp adws, Microsoft AD DS Web Services, Powershell uses this port | |
1120 | 9418/udp git, Git pack transfer service | |
1121 | 9418/tcp git, Git pack transfer service | |
1122 | 9535/udp mngsuite, LANDesk Management Suite Remote Control | |
1123 | 9535/tcp mngsuite, LANDesk Management Suite Remote Control | |
1124 | 9536/udp laes-bf, IP Fabrics Surveillance buffering function | |
1125 | 9536/tcp laes-bf, IP Fabrics Surveillance buffering function | |
1126 | 9800/udp WebDAV Source | |
1127 | 9800/tcp WebDAV Source | |
1128 | 10000/udp Network Data Management Protocol | |
1129 | 10000/tcp Network Data Management Protocol | |
1130 | 10050/udp Zabbix agent | |
1131 | 10050/tcp Zabbix agent | |
1132 | 10051/udp Zabbix trapper | |
1133 | 10051/tcp Zabbix trapper | |
1134 | 10110/udp NMEA 0183 Navigational Data. Transport of NMEA 0183 sentences over TCP or UDP | |
1135 | 10110/tcp NMEA 0183 Navigational Data. Transport of NMEA 0183 sentences over TCP or UDP | |
1136 | 11001/udp metasys ( Johnson Controls Metasys java AC control environment ) | |
1137 | 11001/tcp metasys ( Johnson Controls Metasys java AC control environment ) | |
1138 | 11112/udp ACR/NEMA Digital Imaging and Communications in Medicine (DICOM) | |
1139 | 11112/tcp ACR/NEMA Digital Imaging and Communications in Medicine (DICOM) | |
1140 | 11371/udp OpenPGP HTTP key server | |
1141 | 11371/tcp OpenPGP HTTP key server | |
1142 | 13720/udp Symantec NetBackup-bprd (formerly VERITAS) | |
1143 | 13720/tcp Symantec NetBackup-bprd (formerly VERITAS) | |
1144 | 13721/udp Symantec NetBackup-bpdbm (formerly VERITAS) | |
1145 | 13721/tcp Symantec NetBackup-bpdbm (formerly VERITAS) | |
1146 | 13724/udp Symantec Network Utility-vnetd (formerly VERITAS) | |
1147 | 13724/tcp Symantec Network Utility-vnetd (formerly VERITAS) | |
1148 | 13782/udp Symantec NetBackup-bpcd (formerly VERITAS) | |
1149 | 13782/tcp Symantec NetBackup-bpcd (formerly VERITAS) | |
1150 | 13783/udp Symantec VOPIED protocol (formerly VERITAS) | |
1151 | 13783/tcp Symantec VOPIED protocol (formerly VERITAS) | |
1152 | 13785/udp Symantec NetBackup Database-nbdb (formerly VERITAS) | |
1153 | 13785/tcp Symantec NetBackup Database-nbdb (formerly VERITAS) | |
1154 | 13786/udp Symantec nomdb (formerly VERITAS) | |
1155 | 13786/tcp Symantec nomdb (formerly VERITAS) | |
1156 | 15345/udp XPilot Contact | |
1157 | 15345/tcp XPilot Contact | |
1158 | 17500/udp Dropbox LanSync Protocol (db-lsp); used to synchronize file catalogs between Dropbox clients on a local network. | |
1159 | 17500/tcp Dropbox LanSync Protocol (db-lsp); used to synchronize file catalogs between Dropbox clients on a local network. | |
1160 | 19813/udp 4D database Client Server Communication | |
1161 | 19813/tcp 4D database Client Server Communication | |
1162 | 24465/udp Tonido Directory Server for Tonido which is a Personal Web App and P2P platform | |
1163 | 24465/tcp Tonido Directory Server for Tonido which is a Personal Web App and P2P platform | |
1164 | 24554/udp BINKP, Fidonet mail transfers over TCP/IP | |
1165 | 24554/tcp BINKP, Fidonet mail transfers over TCP/IP | |
1166 | 26000/udp id Software's Quake server | |
1167 | 26000/tcp id Software's Quake server | |
1168 | 27000/udp FlexNet Publisher's License server (from the range of default ports) | |
1169 | 27000/tcp FlexNet Publisher's License server (from the range of default ports) | |
1170 | 27001/udp FlexNet Publisher's License server (from the range of default ports) | |
1171 | 27001/tcp FlexNet Publisher's License server (from the range of default ports) | |
1172 | 27002/udp FlexNet Publisher's License server (from the range of default ports) | |
1173 | 27002/tcp FlexNet Publisher's License server (from the range of default ports) | |
1174 | 27003/udp FlexNet Publisher's License server (from the range of default ports) | |
1175 | 27003/tcp FlexNet Publisher's License server (from the range of default ports) | |
1176 | 27004/udp FlexNet Publisher's License server (from the range of default ports) | |
1177 | 27004/tcp FlexNet Publisher's License server (from the range of default ports) | |
1178 | 27005/udp FlexNet Publisher's License server (from the range of default ports) | |
1179 | 27005/tcp FlexNet Publisher's License server (from the range of default ports) | |
1180 | 27006/udp FlexNet Publisher's License server (from the range of default ports) | |
1181 | 27006/tcp FlexNet Publisher's License server (from the range of default ports) | |
1182 | 27007/udp FlexNet Publisher's License server (from the range of default ports) | |
1183 | 27007/tcp FlexNet Publisher's License server (from the range of default ports) | |
1184 | 27008/udp FlexNet Publisher's License server (from the range of default ports) | |
1185 | 27008/tcp FlexNet Publisher's License server (from the range of default ports) | |
1186 | 27009/udp FlexNet Publisher's License server (from the range of default ports) | |
1187 | 27009/tcp FlexNet Publisher's License server (from the range of default ports) | |
1188 | 33434/udp traceroute | |
1189 | 33434/tcp traceroute | |
1190 | 40000/udp SafetyNET p a real-time Industrial Ethernet protocol | |
1191 | 40000/tcp SafetyNET p a real-time Industrial Ethernet protocol | |
1192 | 44818/udp EtherNet/IP explicit messaging | |
1193 | 44818/tcp EtherNet/IP explicit messaging | |
1194 | 47808/udp BACnet Building Automation and Control Networks (4780810 = BAC016) | |
1195 | 47808/tcp BACnet Building Automation and Control Networks (4780810 = BAC016) | |
1196 | 49151/udp Reserved | |
1197 | 49151/tcp Reserved |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2019 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | """ |
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 urllib.parse import urlsplit | |
7 | import socket | |
8 | import re | |
9 | import os | |
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 | |
18 | ||
19 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
20 | ||
21 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] | |
22 | ||
23 | current_path = os.path.abspath(os.getcwd()) | |
24 | ||
25 | __author__ = "Francisco Amato" | |
26 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
27 | __credits__ = ["Francisco Amato"] | |
28 | __version__ = "1.0.0" | |
29 | __maintainer__ = "Francisco Amato" | |
30 | __email__ = "[email protected]" | |
31 | __status__ = "Development" | |
32 | ||
33 | ||
34 | class AcunetixXmlParser: | |
35 | """ | |
36 | The objective of this class is to parse an xml file generated by | |
37 | the acunetix tool. | |
38 | ||
39 | TODO: Handle errors. | |
40 | TODO: Test acunetix output version. Handle what happens if | |
41 | the parser doesn't support it. | |
42 | TODO: Test cases. | |
43 | ||
44 | @param acunetix_xml_filepath A proper xml generated by acunetix | |
45 | """ | |
46 | ||
47 | def __init__(self, xml_output): | |
48 | tree = self.parse_xml(xml_output) | |
49 | if tree: | |
50 | self.sites = list(self.get_items(tree)) | |
51 | else: | |
52 | self.sites = [] | |
53 | ||
54 | def parse_xml(self, xml_output): | |
55 | """ | |
56 | Open and parse an xml file. | |
57 | ||
58 | TODO: Write custom parser to just read the nodes that we need instead | |
59 | of reading the whole file. | |
60 | ||
61 | @return xml_tree An xml tree instance. None if error. | |
62 | """ | |
63 | try: | |
64 | tree = ET.fromstring(xml_output) | |
65 | except SyntaxError as err: | |
66 | print("SyntaxError: %s. %s", err, xml_output) | |
67 | return None | |
68 | ||
69 | return tree | |
70 | ||
71 | def get_items(self, tree): | |
72 | """ | |
73 | @return items A list of Host instances | |
74 | """ | |
75 | ||
76 | for node in tree.findall('Scan'): | |
77 | yield Site(node) | |
78 | ||
79 | ||
80 | def get_attrib_from_subnode(xml_node, subnode_xpath_expr, attrib_name): | |
81 | """ | |
82 | Finds a subnode in the item node and the retrieves a value from it | |
83 | ||
84 | @return An attribute value | |
85 | """ | |
86 | global ETREE_VERSION | |
87 | node = None | |
88 | ||
89 | if ETREE_VERSION[0] <= 1 and ETREE_VERSION[1] < 3: | |
90 | ||
91 | match_obj = re.search("([^\@]+?)\[\@([^=]*?)=\'([^\']*?)\'", subnode_xpath_expr) | |
92 | ||
93 | if match_obj is not None: | |
94 | node_to_find = match_obj.group(1) | |
95 | xpath_attrib = match_obj.group(2) | |
96 | xpath_value = match_obj.group(3) | |
97 | for node_found in xml_node.findall(node_to_find): | |
98 | if node_found.attrib[xpath_attrib] == xpath_value: | |
99 | node = node_found | |
100 | break | |
101 | else: | |
102 | node = xml_node.find(subnode_xpath_expr) | |
103 | ||
104 | else: | |
105 | node = xml_node.find(subnode_xpath_expr) | |
106 | ||
107 | if node is not None: | |
108 | return node.get(attrib_name) | |
109 | ||
110 | return None | |
111 | ||
112 | ||
113 | class Site: | |
114 | ||
115 | def __init__(self, item_node): | |
116 | self.node = item_node | |
117 | url_data = self.get_url(self.node) | |
118 | ||
119 | self.protocol = url_data.scheme | |
120 | self.host = url_data.hostname | |
121 | ||
122 | # Use the port in the URL if it is defined, or 80 or 443 by default | |
123 | self.port = url_data.port or (443 if url_data.scheme == "https" else 80) | |
124 | ||
125 | self.ip = self.resolve(self.host) | |
126 | self.os = self.get_text_from_subnode('Os') | |
127 | self.banner = self.get_text_from_subnode('Banner') | |
128 | self.items = [] | |
129 | for alert in self.node.findall('ReportItems/ReportItem'): | |
130 | self.items.append(Item(alert)) | |
131 | ||
132 | def get_text_from_subnode(self, subnode_xpath_expr): | |
133 | """ | |
134 | Finds a subnode in the host node and the retrieves a value from it. | |
135 | ||
136 | @return An attribute value | |
137 | """ | |
138 | sub_node = self.node.find(subnode_xpath_expr) | |
139 | if sub_node is not None: | |
140 | return sub_node.text | |
141 | ||
142 | 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 | ||
152 | def get_url(self, node): | |
153 | url = self.get_text_from_subnode('StartURL') | |
154 | url_data = urlsplit(url) | |
155 | if not url_data.scheme: | |
156 | # Getting url from subnode 'Crawler' | |
157 | url_aux = get_attrib_from_subnode(node, 'Crawler', 'StartUrl') | |
158 | url_data = urlsplit(url_aux) | |
159 | ||
160 | return url_data | |
161 | ||
162 | ||
163 | class Item: | |
164 | """ | |
165 | An abstract representation of a Item | |
166 | ||
167 | ||
168 | @param item_node A item_node taken from an acunetix xml tree | |
169 | """ | |
170 | ||
171 | def __init__(self, item_node): | |
172 | self.node = item_node | |
173 | self.name = self.get_text_from_subnode('Name') | |
174 | self.severity = self.get_text_from_subnode('Severity') | |
175 | self.request = self.get_text_from_subnode('TechnicalDetails/Request') | |
176 | self.response = self.get_text_from_subnode('TechnicalDetails/Response') | |
177 | self.parameter = self.get_text_from_subnode('Parameter') | |
178 | self.uri = self.get_text_from_subnode('Affects') | |
179 | self.desc = self.get_text_from_subnode('Description') | |
180 | ||
181 | if self.get_text_from_subnode('Recommendation'): | |
182 | self.resolution = self.get_text_from_subnode('Recommendation') | |
183 | else: | |
184 | self.resolution = "" | |
185 | ||
186 | if self.get_text_from_subnode('reference'): | |
187 | self.desc += "\nDetails: " + self.get_text_from_subnode('Details') | |
188 | else: | |
189 | self.desc += "" | |
190 | ||
191 | # Add path and params to the description to create different IDs if at | |
192 | # least one of this fields is different | |
193 | if self.uri: | |
194 | self.desc += '\nPath: ' + self.uri | |
195 | if self.parameter: | |
196 | self.desc += '\nParameter: ' + self.parameter | |
197 | ||
198 | self.ref = [] | |
199 | for n in item_node.findall('References/Reference'): | |
200 | n2 = n.find('URL') | |
201 | self.ref.append(n2.text) | |
202 | ||
203 | def get_text_from_subnode(self, subnode_xpath_expr): | |
204 | """ | |
205 | Finds a subnode in the host node and the retrieves a value from it. | |
206 | ||
207 | @return An attribute value | |
208 | """ | |
209 | sub_node = self.node.find(subnode_xpath_expr) | |
210 | if sub_node is not None: | |
211 | return sub_node.text | |
212 | ||
213 | return None | |
214 | ||
215 | ||
216 | class AcunetixPlugin(PluginXMLFormat): | |
217 | """ | |
218 | Example plugin to parse acunetix output. | |
219 | """ | |
220 | ||
221 | def __init__(self): | |
222 | super().__init__() | |
223 | self.identifier_tag = "ScanGroup" | |
224 | self.id = "Acunetix" | |
225 | self.name = "Acunetix XML Output Plugin" | |
226 | self.plugin_version = "0.0.1" | |
227 | self.version = "9" | |
228 | self.framework_version = "1.0.0" | |
229 | self.options = None | |
230 | self._current_output = None | |
231 | self.target = None | |
232 | ||
233 | ||
234 | def parseOutputString(self, output, debug=False): | |
235 | """ | |
236 | This method will discard the output the shell sends, it will read it | |
237 | from the xml where it expects it to be present. | |
238 | ||
239 | NOTE: if 'debug' is true then it is being run from a test case and the | |
240 | output being sent is valid. | |
241 | """ | |
242 | parser = AcunetixXmlParser(output) | |
243 | ||
244 | for site in parser.sites: | |
245 | if site.ip is None: | |
246 | continue | |
247 | host = [] | |
248 | if site.host != site.ip: | |
249 | host = [site.host] | |
250 | h_id = self.createAndAddHost(site.ip, site.os) | |
251 | i_id = self.createAndAddInterface( | |
252 | h_id, | |
253 | site.ip, | |
254 | ipv4_address=site.ip, | |
255 | hostname_resolution=host) | |
256 | s_id = self.createAndAddServiceToInterface( | |
257 | h_id, | |
258 | i_id, | |
259 | "http", | |
260 | "tcp", | |
261 | ports=[site.port], | |
262 | version=site.banner, | |
263 | status='open') | |
264 | for item in site.items: | |
265 | self.createAndAddVulnWebToService( | |
266 | h_id, | |
267 | s_id, | |
268 | item.name, | |
269 | item.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 | del parser | |
279 | ||
280 | ||
281 | def setHost(self): | |
282 | pass | |
283 | ||
284 | ||
285 | def createPlugin(): | |
286 | return AcunetixPlugin() | |
287 | ||
288 | ||
289 | if __name__ == "__main__": | |
290 | import sys | |
291 | import os | |
292 | if len(sys.argv) == 2: | |
293 | report_file = sys.argv[1] | |
294 | if os.path.isfile(report_file): | |
295 | plugin = createPlugin() | |
296 | plugin.processReport(report_file) | |
297 | print(plugin.get_json()) | |
298 | else: | |
299 | print(f"Report not found: {report_file}") | |
300 | else: | |
301 | print(f"USAGE {sys.argv[0]} REPORT_FILE") |
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 | from faraday_plugins.plugins.plugin import PluginBase | |
6 | import socket | |
7 | import re | |
8 | import os | |
9 | ||
10 | current_path = os.path.abspath(os.getcwd()) | |
11 | ||
12 | ||
13 | class AmapPlugin(PluginBase): | |
14 | """ Example plugin to parse amap output.""" | |
15 | ||
16 | def __init__(self): | |
17 | super().__init__() | |
18 | self.id = "Amap" | |
19 | self.name = "Amap Output Plugin" | |
20 | self.plugin_version = "0.0.3" | |
21 | self.version = "5.4" | |
22 | self.options = None | |
23 | self._current_output = None | |
24 | self._command_regex = re.compile(r'^(amap|sudo amap).*?') | |
25 | self._hosts = [] | |
26 | ||
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 | ||
35 | services = {} | |
36 | for line in output.split('\n'): | |
37 | if line.startswith('#'): | |
38 | continue | |
39 | ||
40 | fields = self.get_info(line) | |
41 | ||
42 | if len(fields) < 6: | |
43 | continue | |
44 | ||
45 | address = fields[0] | |
46 | h_id = self.createAndAddHost(address) | |
47 | ||
48 | port = fields[1] | |
49 | protocol = fields[2] | |
50 | port_status = fields[3] | |
51 | ||
52 | identification = fields[5] | |
53 | printable_banner = fields[6] | |
54 | ||
55 | if port in services.keys(): | |
56 | if identification != 'unidentified': | |
57 | services[port][5] += ', ' + identification | |
58 | else: | |
59 | services[port] = [ | |
60 | address, | |
61 | port, | |
62 | protocol, | |
63 | port_status, | |
64 | None, | |
65 | identification, | |
66 | printable_banner, | |
67 | None] | |
68 | ||
69 | args = {} | |
70 | ||
71 | if self.args.__getattribute__("6"): | |
72 | self.ip = self.get_ip_6(self.args.m) | |
73 | args['ipv6_address'] = address | |
74 | else: | |
75 | self.ip = self.getAddress(self.args.m) | |
76 | args['ipv4_address'] = address | |
77 | ||
78 | if address != self.args.m: | |
79 | args['hostname_resolution'] = [self.args.m] | |
80 | ||
81 | i_id = self.createAndAddInterface(h_id, name=address, **args) | |
82 | ||
83 | for key in services: | |
84 | service = services.get(key) | |
85 | self.createAndAddServiceToInterface( | |
86 | h_id, | |
87 | i_id, | |
88 | service[5], | |
89 | service[2], | |
90 | ports=[service[1]], | |
91 | status=service[3], | |
92 | description=service[6]) | |
93 | ||
94 | return True | |
95 | ||
96 | file_arg_re = re.compile(r"^.*(-o \s*[^\s]+\s+(?:-m|)).*$") | |
97 | ||
98 | def get_info(self, data): | |
99 | if self.args.__getattribute__("6"): | |
100 | f = re.search( | |
101 | r"^\[(.*)\]:(.*):(.*):(.*):(.*):(.*):(.*):(.*)", | |
102 | data) | |
103 | ||
104 | return [ | |
105 | f.group(1), | |
106 | f.group(2), | |
107 | f.group(3), | |
108 | f.group(4), | |
109 | f.group(5), | |
110 | f.group(6), | |
111 | f.group(7), | |
112 | f.group(8)] if f else [] | |
113 | ||
114 | else: | |
115 | return data.split(':') | |
116 | ||
117 | def get_ip_6(self, host, port=0): | |
118 | alladdr = socket.getaddrinfo(host, port) | |
119 | ip6 = list(filter( | |
120 | lambda x: x[0] == socket.AF_INET6, | |
121 | alladdr)) | |
122 | ||
123 | return ip6[0][4][0] | |
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 | def setHost(self): | |
135 | pass | |
136 | ||
137 | ||
138 | def createPlugin(): | |
139 | return AmapPlugin() | |
140 | ||
141 | if __name__ == "__main__": | |
142 | import sys | |
143 | import os | |
144 | if len(sys.argv) == 2: | |
145 | report_file = sys.argv[1] | |
146 | if os.path.isfile(report_file): | |
147 | plugin = createPlugin() | |
148 | plugin.processReport(report_file) | |
149 | print(plugin.get_json()) | |
150 | else: | |
151 | print(f"Report not found: {report_file}") | |
152 | else: | |
153 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
154 | # I'm Py3 |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2017 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | """ | |
6 | # I'm Py3⏎ |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | """ | |
4 | Faraday Penetration Test IDE | |
5 | Copyright (C) 2017 Infobyte LLC (http://www.infobytesec.com/) | |
6 | See the file 'doc/LICENSE' for the license information | |
7 | """ | |
8 | ||
9 | import socket | |
10 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
11 | from lxml import objectify | |
12 | from urllib.parse import urlparse | |
13 | ||
14 | __author__ = "Alejando Parodi, Ezequiel Tavella" | |
15 | __copyright__ = "Copyright (c) 2015, Infobyte LLC" | |
16 | __credits__ = ["Alejando Parodi", "Ezequiel Tavella"] | |
17 | __license__ = "" | |
18 | __version__ = "1.0" | |
19 | __maintainer__ = "Ezequiel Tavella" | |
20 | __status__ = "Development" | |
21 | ||
22 | ||
23 | ||
24 | def cleaner_unicode(string): | |
25 | return string | |
26 | # if string is not None: | |
27 | # return string.encode('ascii', errors='backslashreplace') | |
28 | # else: | |
29 | # return string | |
30 | ||
31 | ||
32 | class AppscanParser(): | |
33 | ||
34 | def __init__(self, output, logger): | |
35 | self.issue_list = [] | |
36 | self.logger = logger | |
37 | self.obj_xml = objectify.fromstring(output.encode('utf-8')) | |
38 | ||
39 | def parse_issues(self): | |
40 | issue_type = self.parse_issue_type() | |
41 | for issue in self.obj_xml["issue-group"]["item"]: | |
42 | issue_data = issue_type[issue['issue-type']['ref']] | |
43 | obj_issue = {} | |
44 | obj_issue["name"] = issue_data["name"] | |
45 | obj_issue['advisory'] = issue_data["advisory"] | |
46 | if "cve" in issue_data: | |
47 | obj_issue['cve'] = issue_data["cve"].text | |
48 | obj_issue['url'] = self.get_url(issue['url']['ref'].text) | |
49 | obj_issue['cvss_score'] = issue["cvss-score"].text | |
50 | obj_issue['response'] = self.get_response(issue) | |
51 | obj_issue['request'] = issue['variant-group']['item']["test-http-traffic"].text | |
52 | obj_issue['method'] = self.get_method(issue['variant-group']['item']["test-http-traffic"].text) | |
53 | obj_issue['severity'] = issue['severity'].text | |
54 | obj_issue['issue-description'] = self.parse_advisory_group(issue_data['advisory']) | |
55 | for recommendation in self.obj_xml["fix-recommendation-group"]["item"]: | |
56 | full_data = "" | |
57 | if recommendation.attrib['id'] == issue_data["fix-recommendation"]: | |
58 | for data in recommendation['general']['fixRecommendation']["text"]: | |
59 | full_data += '' + data | |
60 | obj_issue["recomendation"] = full_data | |
61 | if hasattr(recommendation['general']['fixRecommendation'], 'link'): | |
62 | obj_issue["ref_link"] = recommendation['general']['fixRecommendation']['link'].text | |
63 | self.issue_list.append(obj_issue) | |
64 | return self.issue_list | |
65 | ||
66 | def parse_hosts(self): | |
67 | hosts_list = [] | |
68 | for host in self.obj_xml['scan-configuration']['scanned-hosts']['item']: | |
69 | hosts_dict = {} | |
70 | hosts_dict['ip'] = socket.gethostbyname(host['host'].text) | |
71 | hosts_dict['hostname'] = host['host'].text | |
72 | hosts_dict['os'] = host['operating-system'].text | |
73 | hosts_dict['port'] = host['port'].text | |
74 | if host['port'].text == '443': | |
75 | hosts_dict['scheme'] = 'https' | |
76 | else: | |
77 | hosts_dict['scheme'] = 'http' | |
78 | hosts_list.append(hosts_dict) | |
79 | return hosts_list | |
80 | ||
81 | def parse_issue_type(self): | |
82 | res = {} | |
83 | for issue_type in self.obj_xml["issue-type-group"]["item"]: | |
84 | res[issue_type.attrib['id']] = { | |
85 | 'name': issue_type.name.text, | |
86 | 'advisory': issue_type["advisory"]["ref"].text, | |
87 | 'fix-recommendation': issue_type["fix-recommendation"]["ref"].text | |
88 | } | |
89 | if "cve" in issue_type: | |
90 | res[issue_type.attrib['id']] = {'cve': issue_type["cve"].text} | |
91 | return res | |
92 | ||
93 | def parse_advisory_group(self, advisory): | |
94 | """ | |
95 | Function that parse advisory-group in order to get the item's description | |
96 | """ | |
97 | for item in self.obj_xml["advisory-group"]["item"]: | |
98 | if item.attrib['id'] == advisory: | |
99 | return item['advisory']['testTechnicalDescription']['text'].text | |
100 | ||
101 | def get_url(self, ref): | |
102 | for item in self.obj_xml['url-group']['item']: | |
103 | if item.attrib['id'] == ref: | |
104 | return item['name'].text | |
105 | ||
106 | def get_method(self, http_traffic): | |
107 | methods_list = ['GET', 'POST', 'PUT', 'DELETE', 'CONNECT', 'PATCH', 'HEAD', 'OPTIONS'] | |
108 | try: | |
109 | if http_traffic: | |
110 | for item in methods_list: | |
111 | if http_traffic.startswith(item): | |
112 | return item | |
113 | except TypeError: | |
114 | return None | |
115 | return None | |
116 | ||
117 | def get_response(self, node): | |
118 | try: | |
119 | response = node['variant-group']['item']['issue-information']["testResponseChunk"].text | |
120 | return response | |
121 | except AttributeError: | |
122 | return None | |
123 | ||
124 | def get_scan_information(self): | |
125 | ||
126 | scan_information = "File: " + self.obj_xml["scan-information"]["scan-file-name"]\ | |
127 | + "\nStart: " + self.obj_xml["scan-information"]["scan-date-and-time"]\ | |
128 | + "\nSoftware: " + self.obj_xml["scan-information"]["product-name"]\ | |
129 | + "\nVersion: " + self.obj_xml["scan-information"]["product-version"]\ | |
130 | + "\nScanner Elapsed time: " + self.obj_xml["scan-summary"]["scan-Duration"] | |
131 | ||
132 | return scan_information | |
133 | ||
134 | ||
135 | class AppscanPlugin(PluginXMLFormat): | |
136 | """ Example plugin to parse Appscan XML report""" | |
137 | ||
138 | def __init__(self): | |
139 | super().__init__() | |
140 | self.identifier_tag = "xml-report" | |
141 | self.id = "Appscan" | |
142 | self.name = "Appscan XML Plugin" | |
143 | self.plugin_version = "0.0.1" | |
144 | self.options = None | |
145 | self.open_options = {"mode": "r", "encoding": "utf-8"} | |
146 | ||
147 | def parseOutputString(self, output, debug=False): | |
148 | try: | |
149 | parser = AppscanParser(output, self.logger) | |
150 | issues = parser.parse_issues() | |
151 | scanned_hosts = parser.parse_hosts() | |
152 | hosts_dict = {} | |
153 | for host in scanned_hosts: | |
154 | host_id = self.createAndAddHost(host['ip'], os=host['os'], hostnames=[host['hostname']]) | |
155 | service_id = self.createAndAddServiceToHost( | |
156 | host_id, | |
157 | host['scheme'], | |
158 | ports=[host['port']], | |
159 | protocol="tcp?HTTP") | |
160 | if host['port']: | |
161 | key_url = f"{host['scheme']}://{host['hostname']}:{host['port']}" | |
162 | else: | |
163 | key_url = f"{host['scheme']}://{host['hostname']}" | |
164 | hosts_dict[key_url] = {'host_id': host_id, 'service_id': service_id} | |
165 | for issue in issues: | |
166 | url_parsed = urlparse(str(issue['url'])) | |
167 | url_string = '://'.join([url_parsed.scheme, url_parsed.netloc]) | |
168 | for key in hosts_dict: | |
169 | if url_string == key: | |
170 | h_id = hosts_dict[key]['host_id'] | |
171 | s_id = hosts_dict[key]['service_id'] | |
172 | refs = [] | |
173 | if "ref_link" in issue: | |
174 | refs.append(f"Fix link: {issue['ref_link']}" ) | |
175 | if "cvss_score" in issue: | |
176 | refs.append(f"CVSS Score: {issue['cvss_score']}") | |
177 | if "cve" in issue: | |
178 | refs.append(f"CVE: {issue['cve']}") | |
179 | if "advisory" in issue: | |
180 | refs.append(f"Advisory: {issue['advisory']}") | |
181 | self.createAndAddVulnWebToService( | |
182 | h_id, | |
183 | s_id, | |
184 | cleaner_unicode(issue["name"]), | |
185 | desc=cleaner_unicode(issue["issue_description"]) if "issue_description" in issue else "", | |
186 | ref=refs, | |
187 | severity=issue["severity"], | |
188 | resolution=cleaner_unicode(issue["recomendation"]), | |
189 | website=url_parsed.netloc, | |
190 | path=url_parsed.path, | |
191 | request=cleaner_unicode(issue["request"]) if "request" in issue else "", | |
192 | response=cleaner_unicode(issue["response"]) if issue["response"] else "", | |
193 | method=issue["method"] if issue["method"] else "") | |
194 | except Exception as e: | |
195 | self.logger.error("Parsing Output Error: %s", e) | |
196 | ||
197 | ||
198 | def createPlugin(): | |
199 | return AppscanPlugin() | |
200 | ||
201 | if __name__ == "__main__": | |
202 | import sys | |
203 | import os | |
204 | if len(sys.argv) == 2: | |
205 | report_file = sys.argv[1] | |
206 | if os.path.isfile(report_file): | |
207 | plugin = createPlugin() | |
208 | plugin.processReport(report_file) | |
209 | print(plugin.get_json()) | |
210 | else: | |
211 | print(f"Report not found: {report_file}") | |
212 | else: | |
213 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
214 | # 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 | # I'm Py3⏎ |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | """ | |
4 | Faraday Penetration Test IDE | |
5 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
6 | See the file 'doc/LICENSE' for the license information | |
7 | """ | |
8 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
9 | import socket | |
10 | import random | |
11 | import re | |
12 | ||
13 | try: | |
14 | import xml.etree.cElementTree as ET | |
15 | except ImportError: | |
16 | import xml.etree.ElementTree as ET | |
17 | ||
18 | __author__ = 'Ezequiel Tavella' | |
19 | __copyright__ = 'Copyright 2016, Faraday Project' | |
20 | __credits__ = ['Ezequiel Tavella', 'Matías Ariel Ré Medina', 'Conrad Stein K'] | |
21 | __license__ = '' | |
22 | __version__ = '1.0.2' | |
23 | __status__ = 'Development' | |
24 | ||
25 | ||
26 | class ArachniXmlParser(): | |
27 | ||
28 | def __init__(self, xml_output): | |
29 | self.tree = self.parse_xml(xml_output) | |
30 | if self.tree: | |
31 | self.issues = self.getIssues(self.tree) | |
32 | self.plugins = self.getPlugins(self.tree) | |
33 | self.system = self.getSystem(self.tree) | |
34 | else: | |
35 | self.system = None | |
36 | self.issues = None | |
37 | self.plugins = None | |
38 | ||
39 | def parse_xml(self, xml_output): | |
40 | try: | |
41 | tree = ET.fromstring(xml_output) | |
42 | except SyntaxError as err: | |
43 | print('SyntaxError In xml: %s. %s' % (err, xml_output)) | |
44 | return None | |
45 | ||
46 | return tree | |
47 | ||
48 | def getIssues(self, tree): | |
49 | ||
50 | # Get vulnerabilities. | |
51 | issues_tree = tree.find('issues') | |
52 | for self.issue_node in issues_tree: | |
53 | yield Issue(self.issue_node) | |
54 | ||
55 | def getPlugins(self, tree): | |
56 | ||
57 | # Get info about plugins executed in scan. | |
58 | plugins_tree = tree.find('plugins') | |
59 | return Plugins(plugins_tree) | |
60 | ||
61 | def getSystem(self, tree): | |
62 | ||
63 | # Get options of scan. | |
64 | return System(tree) | |
65 | ||
66 | ||
67 | class Issue(): | |
68 | ||
69 | def __init__(self, issue_node): | |
70 | ||
71 | self.node = issue_node | |
72 | ||
73 | self.name = self.getDesc('name') | |
74 | self.severity = self.getDesc('severity') | |
75 | self.cwe = self.getDesc('cwe') | |
76 | ||
77 | self.remedy_guidance = self.getDesc('remedy_guidance') | |
78 | self.description = self.getDesc('description') | |
79 | ||
80 | self.var = self.getChildTag('vector', 'affected_input_name') | |
81 | self.url = self.getChildTag('vector', 'url') | |
82 | self.method = self.getChildTag('vector', 'method') | |
83 | ||
84 | self.references = self.getReferences() | |
85 | self.parameters = self.getParameters() | |
86 | ||
87 | self.request = self.getRequest() | |
88 | self.response = self.getResponse() | |
89 | ||
90 | def getDesc(self, tag): | |
91 | ||
92 | # Get value of tag xml | |
93 | description = self.node.find(tag) | |
94 | ||
95 | if description is not None and description.text is not None: | |
96 | return description.text.encode('ascii', 'ignore') | |
97 | else: | |
98 | return 'None' | |
99 | ||
100 | def getChildTag(self, main_tag, child_tag): | |
101 | ||
102 | # Get value of tag child xml | |
103 | main_entity = self.node.find(main_tag) | |
104 | ||
105 | if not main_entity: | |
106 | return 'None' | |
107 | ||
108 | result = main_entity.find(child_tag) | |
109 | ||
110 | if result is not None and result.text is not None: | |
111 | return result.text.encode('ascii', 'ignore') | |
112 | else: | |
113 | return 'None' | |
114 | ||
115 | def getReferences(self): | |
116 | """ | |
117 | Returns current issue references on this format | |
118 | {'url': 'http://www.site.com', 'name': 'WebSite'}. | |
119 | """ | |
120 | ||
121 | result = [] | |
122 | ||
123 | references = self.node.find('references') | |
124 | ||
125 | if not references: | |
126 | return result | |
127 | ||
128 | for tag in references.findall('reference'): | |
129 | url = tag.get('url') | |
130 | result.append(url) | |
131 | ||
132 | return result | |
133 | ||
134 | def getParameters(self): | |
135 | ||
136 | # Get parameters of query | |
137 | result = [] | |
138 | ||
139 | parameters = self.node.find('vector').find('inputs') | |
140 | ||
141 | if not parameters: | |
142 | return '' | |
143 | ||
144 | for param in parameters.findall('input'): | |
145 | name = param.get('name') | |
146 | result.append(name) | |
147 | ||
148 | return ' - '.join(result) | |
149 | ||
150 | def getRequest(self): | |
151 | ||
152 | # Get data about request. | |
153 | try: | |
154 | ||
155 | raw_data = self.node.find('page').find('request').find('raw') | |
156 | data = raw_data.text.encode('ascii', 'ignore') | |
157 | return data | |
158 | ||
159 | except: | |
160 | return 'None' | |
161 | ||
162 | def getResponse(self): | |
163 | ||
164 | # Get data about response. | |
165 | try: | |
166 | ||
167 | raw_data = self.node.find('page').find( | |
168 | 'response').find('raw_headers') | |
169 | data = raw_data.text.encode('ascii', 'ignore') | |
170 | return data | |
171 | ||
172 | except: | |
173 | return 'None' | |
174 | ||
175 | ||
176 | class System(): | |
177 | ||
178 | def __init__(self, node): | |
179 | ||
180 | self.node = node | |
181 | ||
182 | self.user_agent = 'None' | |
183 | self.url = 'None' | |
184 | self.audited_elements = 'None' | |
185 | self.modules = 'None' | |
186 | self.cookies = 'None' | |
187 | ||
188 | self.getOptions() | |
189 | ||
190 | self.version = self.getDesc('version') | |
191 | self.start_time = self.getDesc('start_datetime') | |
192 | self.finish_time = self.getDesc('finish_datetime') | |
193 | ||
194 | self.note = self.getNote() | |
195 | ||
196 | def getOptions(self): | |
197 | ||
198 | # Get values of options scan | |
199 | options = self.node.find('options') | |
200 | if options: | |
201 | options_string = options.text | |
202 | else: | |
203 | return | |
204 | ||
205 | ||
206 | regex_modules = re.compile('checks:\n([\w\d\s\W\D\S]{0,})(platforms:)') | |
207 | regex_user_agent = re.compile('user_agent:(.+)') | |
208 | regex_cookies = re.compile('cookies: {()}') | |
209 | regex_url = re.compile('url:(.+)') | |
210 | ||
211 | regex_audited_elements = re.compile( | |
212 | 'audit:\n([\w\d\s\W\D\S]{0,})input:|session:' | |
213 | ) | |
214 | ||
215 | result = re.search(regex_modules, options_string) | |
216 | if result.group(1): | |
217 | self.modules = result.group(1) | |
218 | ||
219 | result = re.search(regex_user_agent, options_string) | |
220 | if result.group(1): | |
221 | self.user_agent = result.group(1) | |
222 | ||
223 | result = re.search(regex_cookies, options_string) | |
224 | if result.group(1): | |
225 | self.cookies = result.group(1) | |
226 | ||
227 | result = re.search(regex_url, options_string) | |
228 | if result.group(1): | |
229 | self.url = result.group(1) | |
230 | ||
231 | result = re.search(regex_audited_elements, options_string) | |
232 | if result.group(1): | |
233 | self.audited_elements = result.group(1) | |
234 | ||
235 | def getDesc(self, tag): | |
236 | ||
237 | # Return value of tag | |
238 | description = self.node.find(tag) | |
239 | ||
240 | if description and description.text: | |
241 | return description.text | |
242 | else: | |
243 | return 'None' | |
244 | ||
245 | def getNote(self): | |
246 | ||
247 | # Create string with scan information. | |
248 | result = ( | |
249 | 'Scan url:\n' + | |
250 | self.url + | |
251 | '\nUser Agent:\n' + | |
252 | self.user_agent + | |
253 | '\nVersion Arachni:\n' + | |
254 | self.version + | |
255 | '\nStart time:\n' + | |
256 | self.start_time + | |
257 | '\nFinish time:\n' + | |
258 | self.finish_time + | |
259 | '\nAudited Elements:\n' + | |
260 | self.audited_elements + | |
261 | '\nModules:\n' + | |
262 | self.modules + | |
263 | '\nCookies:\n' + | |
264 | self.cookies) | |
265 | ||
266 | return result | |
267 | ||
268 | ||
269 | class Plugins(): | |
270 | ||
271 | """ | |
272 | Support: | |
273 | WAF (Web Application Firewall) Detector (waf_detector) | |
274 | Healthmap (healthmap) | |
275 | """ | |
276 | ||
277 | def __init__(self, plugins_node): | |
278 | ||
279 | self.plugins_node = plugins_node | |
280 | ||
281 | self.healthmap = self.getHealthmap() | |
282 | self.waf = self.getWaf() | |
283 | ||
284 | ||
285 | def getHealthmap(self): | |
286 | ||
287 | # Get info about healthmap | |
288 | healthmap_tree = self.plugins_node.find('healthmap') | |
289 | if not healthmap_tree: | |
290 | return 'None' | |
291 | ||
292 | # Create urls list. | |
293 | list_urls = [] | |
294 | map_results = healthmap_tree.find('results').find('map') | |
295 | ||
296 | for url in map_results: | |
297 | ||
298 | if url.tag == 'with_issues': | |
299 | list_urls.append(f"With Issues: {url.text}") | |
300 | else: | |
301 | list_urls.append(f"Without Issues: {url.text}") | |
302 | ||
303 | def get_value(name, node=None): | |
304 | if not node: | |
305 | node = healthmap_tree | |
306 | x = healthmap_tree.find(name) | |
307 | if x: | |
308 | return x.text | |
309 | else: | |
310 | return "" | |
311 | ||
312 | try: | |
313 | plugin_name = get_value('name') | |
314 | description = get_value('description') | |
315 | results = get_value('results') | |
316 | total = get_value('total', results) | |
317 | with_issues = get_value('with_issues', results) | |
318 | without_issues = get_value('without_issues', results) | |
319 | issue_percentage = get_value('issue_percentage', results) | |
320 | ||
321 | urls = '\n'.join(list_urls) | |
322 | result = (f"Plugin Name: {plugin_name}\nDescription: {description}\nStatistics:\nTotal: {total}" | |
323 | f"\nWith Issues: {with_issues}\nWithout Issues: {without_issues}" | |
324 | f"\nIssues percentage: {issue_percentage}\nResults Map:\n {urls}") | |
325 | return result | |
326 | ||
327 | except: | |
328 | return 'None' | |
329 | ||
330 | def getWaf(self): | |
331 | ||
332 | # Get info about waf plugin. | |
333 | waf_tree = self.plugins_node.find('waf_detector') | |
334 | ||
335 | def get_value(name, node=None): | |
336 | if not node: | |
337 | node = waf_tree | |
338 | x = waf_tree.find(name) | |
339 | if x: | |
340 | return x.text | |
341 | else: | |
342 | return "" | |
343 | ||
344 | try: | |
345 | plugin_name = get_value('name') | |
346 | description = get_value('description') | |
347 | results = waf_tree.find('results') | |
348 | message = get_value('message', results) | |
349 | status = get_value('status', results) | |
350 | result = (f"Plugin Name: {plugin_name}\nDescription: {description}\nResults:" | |
351 | f"\nMessage: {message}\nStatus: {status}") | |
352 | return result | |
353 | except: | |
354 | return 'None' | |
355 | ||
356 | ||
357 | class ArachniPlugin(PluginXMLFormat): | |
358 | ||
359 | # Plugin that parses Arachni's XML report files. | |
360 | ||
361 | def __init__(self): | |
362 | super().__init__() | |
363 | self.identifier_tag = ["report", "arachni_report"] | |
364 | self.id = 'Arachni' | |
365 | self.name = 'Arachni XML Output Plugin' | |
366 | self.plugin_version = '1.0.1' | |
367 | self.version = '1.3.2' | |
368 | self.framework_version = '1.0.0' | |
369 | self.options = None | |
370 | self._command_regex = re.compile(r'^(arachni |\.\/arachni).*?') | |
371 | self.protocol = None | |
372 | self.hostname = None | |
373 | self.port = '80' | |
374 | self.address = None | |
375 | ||
376 | def report_belongs_to(self, **kwargs): | |
377 | if super().report_belongs_to(**kwargs): | |
378 | report_path = kwargs.get("report_path", "") | |
379 | with open(report_path) as f: | |
380 | output = f.read() | |
381 | return re.search("/Arachni/arachni/", output) is not None | |
382 | return False | |
383 | ||
384 | def parseOutputString(self, output, debug=False): | |
385 | """ | |
386 | This method will discard the output the shell sends, it will read it | |
387 | from the xml where it expects it to be present. | |
388 | """ | |
389 | ||
390 | parser = ArachniXmlParser(output) | |
391 | ||
392 | # Check xml parsed ok... | |
393 | if not parser.system: | |
394 | print('Error in xml report... Exiting...') | |
395 | return | |
396 | ||
397 | self.hostname = self.getHostname(parser.system.url) | |
398 | self.address = self.getAddress(self.hostname) | |
399 | ||
400 | # Create host and interface | |
401 | host_id = self.createAndAddHost(self.address) | |
402 | ||
403 | interface_id = self.createAndAddInterface( | |
404 | host_id, | |
405 | self.address, | |
406 | ipv4_address=self.address, | |
407 | hostname_resolution=[self.hostname]) | |
408 | ||
409 | # Create service | |
410 | service_id = self.createAndAddServiceToInterface( | |
411 | host_id, | |
412 | interface_id, | |
413 | self.protocol, | |
414 | 'tcp', | |
415 | ports=[self.port], | |
416 | status='Open', | |
417 | version='', | |
418 | description='') | |
419 | ||
420 | ||
421 | # Create issues. | |
422 | for issue in parser.issues: | |
423 | ||
424 | description = issue.description.replace(' ', ' ').replace('\n', ' ').replace('. ', '.\n\n') | |
425 | resol = issue.remedy_guidance.replace(' ', ' ').replace('\n', ' ').replace('. ', '.\n\n') | |
426 | ||
427 | references = issue.references | |
428 | if issue.cwe != 'None': | |
429 | references.append('CWE-' + issue.cwe) | |
430 | ||
431 | if resol == 'None': | |
432 | resol = '' | |
433 | ||
434 | self.createAndAddVulnWebToService( | |
435 | host_id, | |
436 | service_id, | |
437 | name=issue.name, | |
438 | desc=description, | |
439 | resolution=resol, | |
440 | ref=references, | |
441 | severity=issue.severity, | |
442 | website=self.hostname, | |
443 | path=issue.url, | |
444 | method=issue.method, | |
445 | pname=issue.var, | |
446 | params=issue.parameters, | |
447 | request=issue.request, | |
448 | response=issue.response) | |
449 | ||
450 | return | |
451 | ||
452 | def processCommandString(self, username, current_path, command_string): | |
453 | """ | |
454 | Use bash to run sequentialy arachni and arachni_reporter | |
455 | """ | |
456 | ||
457 | afr_output_file_path = os.path.join( | |
458 | self.data_path, | |
459 | "%s_%s_output-%s.afr" % ( | |
460 | self.get_ws(), | |
461 | self.id, | |
462 | random.uniform(1, 10)) | |
463 | ) | |
464 | ||
465 | report_arg_re = r"^.*(--report-save-path[=\s][^\s]+).*$" | |
466 | arg_match = re.match(report_arg_re,command_string) | |
467 | if arg_match is None: | |
468 | main_cmd = re.sub(r"(^.*?arachni)", | |
469 | r"\1 --report-save-path=%s" % afr_output_file_path, | |
470 | command_string) | |
471 | else: | |
472 | main_cmd = re.sub(arg_match.group(1), | |
473 | r"--report-save-path=%s" % afr_output_file_path, | |
474 | command_string) | |
475 | ||
476 | # add reporter | |
477 | self._output_file_path = re.sub('.afr', '.xml', afr_output_file_path) | |
478 | cmd_prefix_match = re.match(r"(^.*?)arachni ", command_string) | |
479 | cmd_prefix = cmd_prefix_match.group(1) | |
480 | reporter_cmd = "%s%s --reporter=\"xml:outfile=%s\" \"%s\"" % ( | |
481 | cmd_prefix, | |
482 | "arachni_reporter", | |
483 | self._output_file_path, | |
484 | afr_output_file_path) | |
485 | return "/usr/bin/env -- bash -c '%s 2>&1 && if [ -e \"%s\" ];then %s 2>&1;fi'" % (main_cmd, afr_output_file_path, reporter_cmd) | |
486 | ||
487 | ||
488 | def getHostname(self, url): | |
489 | ||
490 | # Strips protocol and gets hostname from URL. | |
491 | reg = re.search( | |
492 | '(http|https|ftp)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*@)*(' | |
493 | '(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' | |
494 | ']|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' | |
495 | '-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' | |
496 | '-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+' | |
497 | '\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pr' | |
498 | 'o|aero|coop|museum|[a-zA-Z]{2}))[\:]*([0-9]+)*([/]*($|[a-zA-Z0-9\.\,\?' | |
499 | '\'\\\+&%\$#\=~_\-]+)).*?$', | |
500 | url | |
501 | ) | |
502 | ||
503 | self.protocol = reg.group(1) | |
504 | self.hostname = reg.group(4) | |
505 | ||
506 | if self.protocol == 'https': | |
507 | self.port = 443 | |
508 | if reg.group(11) is not None: | |
509 | self.port = reg.group(11) | |
510 | ||
511 | return self.hostname | |
512 | ||
513 | def getAddress(self, hostname): | |
514 | ||
515 | # Returns remote IP address from hostname. | |
516 | try: | |
517 | return socket.gethostbyname(hostname) | |
518 | except socket.error as msg: | |
519 | return self.hostname | |
520 | ||
521 | ||
522 | def createPlugin(): | |
523 | return ArachniPlugin() | |
524 | ||
525 | if __name__ == "__main__": | |
526 | import sys | |
527 | import os | |
528 | if len(sys.argv) == 2: | |
529 | report_file = sys.argv[1] | |
530 | if os.path.isfile(report_file): | |
531 | plugin = createPlugin() | |
532 | plugin.processReport(report_file) | |
533 | print(plugin.get_json()) | |
534 | else: | |
535 | print(f"Report not found: {report_file}") | |
536 | else: | |
537 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
538 | # 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 | # 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 | from faraday_plugins.plugins.plugin import PluginBase | |
6 | import re | |
7 | ||
8 | __author__ = "Federico Kirschbaum" | |
9 | __copyright__ = "Copyright 2013, Faraday Project" | |
10 | __credits__ = ["Federico Kirschbaum"] | |
11 | __license__ = "" | |
12 | __version__ = "1.0.0" | |
13 | __maintainer__ = "Federico Kirschbaum" | |
14 | __email__ = "[email protected]" | |
15 | __status__ = "Development" | |
16 | ||
17 | ||
18 | class CmdArpScanPlugin(PluginBase): | |
19 | """ | |
20 | This plugin handles arp-scan command. | |
21 | Basically inserts into the tree the ouput of this tool | |
22 | """ | |
23 | ||
24 | def __init__(self): | |
25 | super().__init__() | |
26 | self.id = "arp-scan" | |
27 | self.name = "arp-scan network scanner" | |
28 | self.plugin_version = "0.0.2" | |
29 | self.version = "1.8.1" | |
30 | self.framework_version = "1.0.0" | |
31 | self.options = None | |
32 | self._current_output = None | |
33 | self._command_regex = re.compile( | |
34 | r'^(sudo arp-scan|\.\/arp-scan|arp-scan).*?') | |
35 | self._host_ip = None | |
36 | ||
37 | def parseOutputString(self, output, debug=False): | |
38 | ||
39 | host_info = re.search( | |
40 | 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)", | |
41 | output) | |
42 | ||
43 | host_mac_addr = re.search(r"([\dA-F]{2}(?:[-:][\dA-F]{2}){5})", output, re.IGNORECASE) | |
44 | ||
45 | if host_info is None: | |
46 | self.logger.info("No hosts detected") | |
47 | else: | |
48 | ||
49 | for line in output.split('\n'): | |
50 | vals = line.split("\t") | |
51 | ||
52 | if len(vals) == 3: | |
53 | ||
54 | if len(vals[0].split(".")) == 4: | |
55 | ||
56 | host = vals[0] | |
57 | h_id = self.createAndAddHost(host) | |
58 | i_id = self.createAndAddInterface(h_id, host, ipv4_address=host, mac=vals[1]) | |
59 | ||
60 | return True | |
61 | ||
62 | def processCommandString(self, username, current_path, command_string): | |
63 | return | |
64 | ||
65 | ||
66 | def createPlugin(): | |
67 | return CmdArpScanPlugin() | |
68 | ||
69 | if __name__ == "__main__": | |
70 | import sys | |
71 | import os | |
72 | if len(sys.argv) == 2: | |
73 | report_file = sys.argv[1] | |
74 | if os.path.isfile(report_file): | |
75 | plugin = createPlugin() | |
76 | plugin.processReport(report_file) | |
77 | print(plugin.get_json()) | |
78 | else: | |
79 | print(f"Report not found: {report_file}") | |
80 | else: | |
81 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
82 | # 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 | # 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 | from faraday_plugins.plugins.plugin import PluginBase | |
6 | import re | |
7 | from urllib.request import urlopen | |
8 | import json | |
9 | ||
10 | __author__ = "Francisco Amato" | |
11 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
12 | __credits__ = ["Francisco Amato"] | |
13 | __license__ = "" | |
14 | __version__ = "1.0.0" | |
15 | __maintainer__ = "Francisco Amato" | |
16 | __email__ = "[email protected]" | |
17 | __status__ = "Development" | |
18 | ||
19 | ||
20 | class BeefPlugin(PluginBase): | |
21 | """ | |
22 | Example plugin to parse beef output. | |
23 | """ | |
24 | ||
25 | def __init__(self): | |
26 | super().__init__() | |
27 | self.id = "Beef" | |
28 | self.name = "BeEF Online Service Plugin" | |
29 | self.plugin_version = "0.0.1" | |
30 | self.version = "0.4.4.9-alpha" | |
31 | self.framework_version = "1.0.0" | |
32 | self.options = None | |
33 | self._current_output = None | |
34 | self.target = None | |
35 | self._command_regex = re.compile(r'^(beef|sudo beef|\.\/beef).*?') | |
36 | ||
37 | self.addSetting("Host", str, "http://127.0.0.1:3000/") | |
38 | self.addSetting( | |
39 | "Authkey", str, "c818c7798ae1da38b45a6406c8dd0d6d4d007098") | |
40 | self.addSetting("Enable", str, "0") | |
41 | ||
42 | def parseOutputString(self, output, debug=False): | |
43 | """ | |
44 | This method will discard the output the shell sends, it will read it from | |
45 | the xml where it expects it to be present. | |
46 | ||
47 | NOTE: if 'debug' is true then it is being run from a test case and the | |
48 | output being sent is valid. | |
49 | """ | |
50 | try: | |
51 | f = urlopen(self.getSetting( | |
52 | "Host") + "/api/hooks?token=" + self.getSetting("Authkey")) | |
53 | data = json.loads(f.read()) | |
54 | except: | |
55 | self.logger.info("[BeEF] - Connection with api") | |
56 | return | |
57 | ||
58 | if "hooked-browsers" in data: | |
59 | ||
60 | for t in ["online", "offlne"]: | |
61 | for h in data["hooked-browsers"][t]: | |
62 | ||
63 | name = str(data["hooked-browsers"][t][h]['name']) | |
64 | version = str(data["hooked-browsers"][t][h]['version']) | |
65 | os = str(data["hooked-browsers"][t][h]['os']) | |
66 | platform = str(data["hooked-browsers"][t][h]['platform']) | |
67 | session = str(data["hooked-browsers"][t][h]['session']) | |
68 | ip = str(data["hooked-browsers"][t][h]['ip']) | |
69 | domain = str(data["hooked-browsers"][t][h]['domain']) | |
70 | port = str(data["hooked-browsers"][t][h]['port']) | |
71 | page_uri = str(data["hooked-browsers"][t][h]['page_uri']) | |
72 | ||
73 | desc = "Client ip:" + ip + \ | |
74 | " has been injected with BeEF using the url:" + page_uri + "\n" | |
75 | ||
76 | desc += "More information:" | |
77 | desc += "\ntype:" + t | |
78 | desc += "\nname:" + name | |
79 | desc += "\nversion:" + version | |
80 | desc += "\nos:" + os | |
81 | desc += "\nplatform:" + platform | |
82 | desc += "\nsession:" + session | |
83 | desc += "\nip:" + ip | |
84 | desc += "\ndomain:" + domain | |
85 | desc += "\nport:" + port | |
86 | desc += "\npage_uri:" + page_uri | |
87 | ||
88 | h_id = self.createAndAddHost(ip) | |
89 | v_id = self.createAndAddVulnToHost( | |
90 | h_id, | |
91 | "BeEF injected " + t + " session:" + session, | |
92 | desc=desc, | |
93 | ref=["http://http://beefproject.com/"], | |
94 | severity=3) | |
95 | ||
96 | def processCommandString(self, username, current_path, command_string): | |
97 | return None | |
98 | ||
99 | def setHost(self): | |
100 | pass | |
101 | ||
102 | ||
103 | def createPlugin(): | |
104 | return BeefPlugin() | |
105 | ||
106 | if __name__ == "__main__": | |
107 | import sys | |
108 | import os | |
109 | if len(sys.argv) == 2: | |
110 | report_file = sys.argv[1] | |
111 | if os.path.isfile(report_file): | |
112 | plugin = createPlugin() | |
113 | plugin.processReport(report_file) | |
114 | print(plugin.get_json()) | |
115 | else: | |
116 | print(f"Report not found: {report_file}") | |
117 | else: | |
118 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
119 | # I'm Py3 |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2018 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) 2018 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | """ | |
5 | import re | |
6 | import socket | |
7 | from urllib.parse import urlparse | |
8 | from faraday_plugins.plugins.plugin import PluginBase | |
9 | ||
10 | __author__ = "Roberto Focke" | |
11 | __copyright__ = "Copyright (c) 2017, Infobyte LLC" | |
12 | __license__ = "" | |
13 | __version__ = "1.0.0" | |
14 | ||
15 | ||
16 | class brutexss (PluginBase): | |
17 | ||
18 | def __init__(self): | |
19 | super().__init__() | |
20 | self.id = "brutexss" | |
21 | self.name = "brutexss" | |
22 | self.plugin_version = "0.0.2" | |
23 | self.version = "1.0.0" | |
24 | self.protocol ='tcp' | |
25 | self._command_regex = re.compile(r'^(sudo brutexss|brutexss|sudo brutexss\.py|brutexss\.py|python brutexss\.py|\.\/brutexss\.py).*?') | |
26 | ||
27 | def parseOutputString(self, output, debug=False): | |
28 | lineas = output.split("\n") | |
29 | parametro = [] | |
30 | found_vuln = False | |
31 | for linea in lineas: | |
32 | if linea.find("is available! Good!") > 0: | |
33 | print(linea) | |
34 | url = re.findall('(?:[-\w.]|(?:%[\da-fA-F]{2}))+', linea)[0] | |
35 | port = 80 | |
36 | if urlparse(url).scheme == 'https': | |
37 | port = 443 | |
38 | netloc_splitted = urlparse(url).netloc.split(':') | |
39 | if len(netloc_splitted) > 1: | |
40 | port = netloc_splitted[1] | |
41 | if linea.find("Vulnerable") > 0 and "No" not in linea: | |
42 | vuln_list = re.findall("\w+", linea) | |
43 | if vuln_list[2] == "Vulnerable": | |
44 | parametro.append(vuln_list[1]) | |
45 | found_vuln=len(parametro) > 0 | |
46 | host_id = self.createAndAddHost(url) | |
47 | address=socket.gethostbyname(url) | |
48 | interface_id = self.createAndAddInterface(host_id,address,ipv4_address=address,hostname_resolution=[url]) | |
49 | service_id = self.createAndAddServiceToInterface(host_id, interface_id, self.protocol, 'tcp', | |
50 | ports=[port], status='Open', version="", description="") | |
51 | if found_vuln: | |
52 | self.createAndAddVulnWebToService(host_id,service_id, name="xss", desc="XSS", ref='', severity='med', | |
53 | website=url, path='', method='', pname='', params=''.join(parametro), | |
54 | request='', response='') | |
55 | ||
56 | def processCommandString(self, username, current_path, command_string): | |
57 | return None | |
58 | ||
59 | ||
60 | def createPlugin(): | |
61 | return brutexss() | |
62 | ||
63 | if __name__ == "__main__": | |
64 | import sys | |
65 | import os | |
66 | if len(sys.argv) == 2: | |
67 | report_file = sys.argv[1] | |
68 | if os.path.isfile(report_file): | |
69 | plugin = createPlugin() | |
70 | plugin.processReport(report_file) | |
71 | print(plugin.get_json()) | |
72 | else: | |
73 | print(f"Report not found: {report_file}") | |
74 | else: | |
75 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
76 | # 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 | # I'm Py3⏎ |
Binary diff not shown
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | """ | |
4 | Faraday Penetration Test IDE | |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
6 | See the file 'doc/LICENSE' for the license information | |
7 | ||
8 | """ | |
9 | import re | |
10 | import os | |
11 | import sys | |
12 | import base64 | |
13 | from bs4 import BeautifulSoup, Comment | |
14 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
15 | from urllib.parse import urlsplit | |
16 | import distutils.util #pylint: disable=import-error | |
17 | ||
18 | ||
19 | try: | |
20 | import xml.etree.cElementTree as ET | |
21 | import xml.etree.ElementTree as ET_ORIG | |
22 | ETREE_VERSION = ET_ORIG.VERSION | |
23 | except ImportError: | |
24 | import xml.etree.ElementTree as ET | |
25 | ETREE_VERSION = ET.VERSION | |
26 | ||
27 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] | |
28 | ||
29 | current_path = os.path.abspath(os.getcwd()) | |
30 | ||
31 | __author__ = "Francisco Amato" | |
32 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
33 | __credits__ = ["Francisco Amato", "Micaela Ranea Sanchez"] | |
34 | __license__ = "" | |
35 | __version__ = "1.1.0" | |
36 | __maintainer__ = "Francisco Amato" | |
37 | __email__ = "[email protected]" | |
38 | __status__ = "Development" | |
39 | ||
40 | ||
41 | class BurpXmlParser: | |
42 | """ | |
43 | The objective of this class is to parse an xml file generated by the burp tool. | |
44 | ||
45 | TODO: Handle errors. | |
46 | TODO: Test burp output version. Handle what happens if the parser doesn't support it. | |
47 | TODO: Test cases. | |
48 | ||
49 | @param burp_xml_filepath A proper xml generated by burp | |
50 | """ | |
51 | ||
52 | def __init__(self, xml_output): | |
53 | ||
54 | self.target = None | |
55 | self.port = "80" | |
56 | self.host = None | |
57 | ||
58 | tree = self.parse_xml(xml_output) | |
59 | if tree: | |
60 | self.items = [data for data in self.get_items(tree)] | |
61 | else: | |
62 | self.items = [] | |
63 | ||
64 | def parse_xml(self, xml_output): | |
65 | """ | |
66 | Open and parse an xml file. | |
67 | ||
68 | TODO: Write custom parser to just read the nodes that we need instead of | |
69 | reading the whole file. | |
70 | ||
71 | @return xml_tree An xml tree instance. None if error. | |
72 | """ | |
73 | try: | |
74 | tree = ET.fromstring(xml_output) | |
75 | except SyntaxError as err: | |
76 | print("SyntaxError: %s. %s" % (err, xml_output)) | |
77 | return None | |
78 | ||
79 | return tree | |
80 | ||
81 | def get_items(self, tree): | |
82 | """ | |
83 | @return items A list of Host instances | |
84 | """ | |
85 | bugtype = '' | |
86 | ||
87 | for node in tree.findall('issue'): | |
88 | yield Item(node) | |
89 | ||
90 | ||
91 | def get_attrib_from_subnode(xml_node, subnode_xpath_expr, attrib_name): | |
92 | """ | |
93 | Finds a subnode in the item node and the retrieves a value from it | |
94 | ||
95 | @return An attribute value | |
96 | """ | |
97 | global ETREE_VERSION | |
98 | node = None | |
99 | ||
100 | if ETREE_VERSION[0] <= 1 and ETREE_VERSION[1] < 3: | |
101 | ||
102 | match_obj = re.search( | |
103 | "([^\@]+?)\[\@([^=]*?)=\'([^\']*?)\'", subnode_xpath_expr) | |
104 | if match_obj is not None: | |
105 | ||
106 | node_to_find = match_obj.group(1) | |
107 | xpath_attrib = match_obj.group(2) | |
108 | xpath_value = match_obj.group(3) | |
109 | for node_found in xml_node.findall(node_to_find): | |
110 | if node_found.attrib[xpath_attrib] == xpath_value: | |
111 | node = node_found | |
112 | break | |
113 | else: | |
114 | node = xml_node.find(subnode_xpath_expr) | |
115 | ||
116 | else: | |
117 | node = xml_node.find(subnode_xpath_expr) | |
118 | ||
119 | if node is not None: | |
120 | return node.get(attrib_name) | |
121 | ||
122 | return None | |
123 | ||
124 | ||
125 | class Item: | |
126 | """ | |
127 | An abstract representation of a Item | |
128 | @param item_node A item_node taken from an burp xml tree | |
129 | """ | |
130 | ||
131 | def __init__(self, item_node): | |
132 | self.node = item_node | |
133 | ||
134 | name = item_node.findall('name')[0] | |
135 | host_node = item_node.findall('host')[0] | |
136 | path = item_node.findall('path')[0] | |
137 | location = item_node.findall('location')[0] | |
138 | severity = item_node.findall('severity')[0] | |
139 | external_id = item_node.findall('type')[0] | |
140 | request = self.decode_binary_node('./requestresponse/request') | |
141 | response = self.decode_binary_node('./requestresponse/response') | |
142 | ||
143 | detail = self.do_clean(item_node.findall('issueDetail')) | |
144 | remediation = self.do_clean(item_node.findall('remediationBackground')) | |
145 | background = self.do_clean(item_node.findall('issueBackground')) | |
146 | ||
147 | self.url = host_node.text | |
148 | ||
149 | url_data = urlsplit(self.url) | |
150 | ||
151 | self.protocol = url_data.scheme | |
152 | self.host = url_data.hostname | |
153 | ||
154 | # Use the port in the URL if it is defined, or 80 or 443 by default | |
155 | self.port = url_data.port or (443 if url_data.scheme == "https" | |
156 | else 80) | |
157 | ||
158 | self.name = name.text | |
159 | self.location = location.text | |
160 | self.path = path.text | |
161 | ||
162 | self.ip = host_node.get('ip') | |
163 | self.url = self.node.get('url') | |
164 | self.severity = severity.text | |
165 | self.request = request | |
166 | self.response = response | |
167 | self.detail = detail | |
168 | self.remediation = remediation | |
169 | self.background = background | |
170 | self.external_id = external_id.text | |
171 | ||
172 | ||
173 | def do_clean(self, value): | |
174 | ||
175 | myreturn = "" | |
176 | if value is not None: | |
177 | if len(value) > 0: | |
178 | myreturn = value[0].text | |
179 | return myreturn | |
180 | ||
181 | def decode_binary_node(self, path): | |
182 | """ | |
183 | Finds a subnode matching `path` and returns its inner text if | |
184 | it has no base64 attribute or its base64 decoded inner text if | |
185 | it has it. | |
186 | """ | |
187 | nodes = self.node.findall(path) | |
188 | try: | |
189 | subnode = nodes[0] | |
190 | except IndexError: | |
191 | return "" | |
192 | encoded = distutils.util.strtobool(subnode.get('base64', 'false')) | |
193 | if encoded: | |
194 | res = base64.b64decode(subnode.text).decode('utf-8', errors="backslashreplace") | |
195 | else: | |
196 | res = subnode.text | |
197 | return "".join([ch for ch in res if ord(ch) <= 128]) | |
198 | ||
199 | def get_text_from_subnode(self, subnode_xpath_expr): | |
200 | """ | |
201 | Finds a subnode in the host node and the retrieves a value from it. | |
202 | @return An attribute value | |
203 | """ | |
204 | ||
205 | sub_node = self.node.find(subnode_xpath_expr) | |
206 | if sub_node is not None: | |
207 | return sub_node.text | |
208 | ||
209 | return None | |
210 | ||
211 | ||
212 | class BurpPlugin(PluginXMLFormat): | |
213 | """ | |
214 | Example plugin to parse burp output. | |
215 | """ | |
216 | ||
217 | def __init__(self): | |
218 | super().__init__() | |
219 | self.identifier_tag = "issues" | |
220 | self.id = "Burp" | |
221 | self.name = "Burp XML Output Plugin" | |
222 | self.plugin_version = "0.0.2" | |
223 | self.version = "1.6.05 BurpPro" | |
224 | self.framework_version = "1.0.0" | |
225 | self.options = None | |
226 | self._current_output = None | |
227 | self.target = None | |
228 | ||
229 | ||
230 | def parseOutputString(self, output, debug=False): | |
231 | ||
232 | parser = BurpXmlParser(output) | |
233 | for item in parser.items: | |
234 | ||
235 | h_id = self.createAndAddHost(item.ip) | |
236 | ||
237 | i_id = self.createAndAddInterface( | |
238 | h_id, | |
239 | item.ip, | |
240 | ipv4_address=item.ip, | |
241 | hostname_resolution=[item.host]) | |
242 | ||
243 | s_id = self.createAndAddServiceToInterface( | |
244 | h_id, | |
245 | i_id, | |
246 | item.protocol, | |
247 | "tcp", | |
248 | ports=[str(item.port)], | |
249 | status="open") | |
250 | ||
251 | desc = "Detail\n" + item.detail | |
252 | if item.background: | |
253 | desc += "\nBackground\n" + item.background | |
254 | desc = self.removeHtml(desc) | |
255 | resolution = self.removeHtml(item.remediation) if item.remediation else "" | |
256 | ||
257 | v_id = self.createAndAddVulnWebToService( | |
258 | h_id, | |
259 | s_id, | |
260 | item.name, | |
261 | desc=desc, | |
262 | severity=item.severity, | |
263 | website=item.host, | |
264 | path=item.path, | |
265 | request=item.request, | |
266 | response=item.response, | |
267 | resolution=resolution, | |
268 | external_id=item.external_id) | |
269 | ||
270 | del parser | |
271 | ||
272 | def processCommandString(self, username, current_path, command_string): | |
273 | return None | |
274 | ||
275 | def removeHtml(self, markup): | |
276 | soup = BeautifulSoup(markup, "html.parser") | |
277 | ||
278 | # Replace line breaks and paragraphs for new lines | |
279 | for tag in soup.find_all(["br", "p"]): | |
280 | tag.append("\n") | |
281 | tag.unwrap() | |
282 | ||
283 | # Replace lists for * and new lines | |
284 | for tag in soup.find_all(["ul", "ol"]): | |
285 | for item in tag.find_all("li"): | |
286 | item.insert_before("* ") | |
287 | item.append("\n") | |
288 | item.unwrap() | |
289 | tag.unwrap() | |
290 | ||
291 | # Remove all other HTML tags | |
292 | for tag in soup.find_all(): | |
293 | tag.unwrap() | |
294 | ||
295 | # Remove all comments | |
296 | for child in soup.children: | |
297 | if isinstance(child, Comment): | |
298 | child.extract() | |
299 | ||
300 | return str(soup) | |
301 | ||
302 | def setHost(self): | |
303 | pass | |
304 | ||
305 | ||
306 | def createPlugin(): | |
307 | return BurpPlugin() | |
308 | ||
309 | ||
310 | if __name__ == "__main__": | |
311 | import sys | |
312 | import os | |
313 | if len(sys.argv) == 2: | |
314 | report_file = sys.argv[1] | |
315 | if os.path.isfile(report_file): | |
316 | plugin = createPlugin() | |
317 | plugin.processReport(report_file) | |
318 | print(plugin.get_json()) | |
319 | else: | |
320 | print(f"Report not found: {report_file}") | |
321 | else: | |
322 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
323 | # 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 | # I'm Py3⏎ |
0 | """ | |
1 | Updated by Mike Zhong, 25 Oct 2017. | |
2 | ||
3 | Faraday Penetration Test IDE | |
4 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
5 | See the file 'doc/LICENSE' for the license information | |
6 | """ | |
7 | import re | |
8 | import socket | |
9 | ||
10 | from faraday_plugins.plugins.plugin import PluginBase | |
11 | ||
12 | __author__ = u"Andres Tarantini" | |
13 | __copyright__ = u"Copyright (c) 2015 Andres Tarantini" | |
14 | __credits__ = [u"Andres Tarantini"] | |
15 | __license__ = u"MIT" | |
16 | __version__ = u"0.0.1" | |
17 | __maintainer__ = u"Andres Tarantini" | |
18 | __email__ = u"[email protected]" | |
19 | __status__ = u"Development" | |
20 | ||
21 | ||
22 | class DigPlugin(PluginBase): | |
23 | """ | |
24 | Handle DiG (http://linux.die.net/man/1/dig) output | |
25 | """ | |
26 | ||
27 | def __init__(self): | |
28 | super().__init__() | |
29 | self.id = u"dig" | |
30 | self.name = u"DiG" | |
31 | self.plugin_version = u"0.0.1" | |
32 | self.version = u"9.9.5-3" | |
33 | self._command_regex = re.compile(r'^(dig).*?') | |
34 | ||
35 | def parseOutputString(self, output): | |
36 | # Ignore all lines that start with ";" | |
37 | parsed_output = [line for line in output.splitlines() if line and line[ | |
38 | 0] != u";"] | |
39 | if not parsed_output: | |
40 | return True | |
41 | ||
42 | # Parse results | |
43 | results = [] | |
44 | answer_section_columns = [u"domain", | |
45 | u"ttl", u"class", u"type", u"data"] | |
46 | for line in parsed_output: | |
47 | line_split = line.split() # the first 4 elements are domain, ttl, class, type; everything else data | |
48 | results.append(dict(zip(answer_section_columns, line_split[:4] + [line_split[4:]] ))) | |
49 | ||
50 | # Create hosts is results information is relevant | |
51 | try: | |
52 | for result in results: | |
53 | relevant_types = [u"A", u"AAAA", u"MX", u"NS", u"SOA", u"TXT"] | |
54 | # TODO implement more types from https://en.wikipedia.org/wiki/List_of_DNS_record_types | |
55 | ||
56 | if result.get(u"type") in relevant_types: | |
57 | ||
58 | # get domain | |
59 | domain = result.get(u"domain") | |
60 | ||
61 | ||
62 | # get IP address (special if type "A") | |
63 | if result.get(u"type") == u"A": # A = IPv4 address from dig | |
64 | ip_address = result.get(u"data")[0] | |
65 | else: # if not, from socket | |
66 | ip_address = socket.gethostbyname(domain) | |
67 | ||
68 | # Create host | |
69 | host_id = self.createAndAddHost(ip_address) | |
70 | ||
71 | # create interface (special if type "AAAA") | |
72 | if result.get(u"type") == u"AAAA": # AAAA = IPv6 address | |
73 | # TODO is there a function to dynamically update the paramter ipv6_address of an already-created interface? | |
74 | ipv6_address = result.get(u"data")[0] | |
75 | interface_id = self.createAndAddInterface( | |
76 | host_id, | |
77 | ip_address, | |
78 | ipv4_address=ip_address, | |
79 | ipv6_address=ipv6_address, | |
80 | hostname_resolution=[domain]) | |
81 | else: | |
82 | interface_id = self.createAndAddInterface( | |
83 | host_id, | |
84 | ip_address, | |
85 | ipv4_address=ip_address, | |
86 | hostname_resolution=[domain]) | |
87 | ||
88 | ||
89 | # all other TYPES that aren't 'A' and 'AAAA' are dealt here: | |
90 | if result.get(u"type") == u"MX": # Mail exchange record | |
91 | mx_priority = result.get(u"data")[0] | |
92 | mx_record = result.get(u"data")[1] | |
93 | ||
94 | service_id = self.createAndAddServiceToInterface( | |
95 | host_id=host_id, | |
96 | interface_id=interface_id, | |
97 | name=mx_record, | |
98 | protocol="SMTP", | |
99 | ports=[25], | |
100 | description="E-mail Server") | |
101 | ||
102 | text = "Priority: " + mx_priority | |
103 | self.createAndAddNoteToService( | |
104 | host_id=host_id, | |
105 | service_id=service_id, | |
106 | name="priority", | |
107 | text=text.encode('ascii', 'ignore')) | |
108 | ||
109 | elif result.get(u"type") == u"NS": # Name server record | |
110 | ns_record = result.get(u"data")[0] | |
111 | self.createAndAddServiceToInterface( | |
112 | host_id=host_id, | |
113 | interface_id=interface_id, | |
114 | name=ns_record, | |
115 | protocol="DNS", | |
116 | ports=[53], | |
117 | description="DNS Server") | |
118 | ||
119 | elif result.get(u"type") == u"SOA": # Start of Authority Record | |
120 | ns_record = result.get(u"data")[0] # primary namer server | |
121 | responsible_party = result.get(u"data")[1] # responsible party of domain | |
122 | timestamp = result.get(u"data")[2] | |
123 | refresh_zone_time = result.get(u"data")[3] | |
124 | retry_refresh_time = result.get(u"data")[4] | |
125 | upper_limit_time = result.get(u"data")[5] | |
126 | negative_result_ttl = result.get(u"data")[6] | |
127 | ||
128 | service_id = self.createAndAddServiceToInterface( | |
129 | host_id=host_id, | |
130 | interface_id=interface_id, | |
131 | name=ns_record, | |
132 | protocol="DNS", | |
133 | ports=[53], | |
134 | description="Authority Record") | |
135 | ||
136 | text = ( | |
137 | "Responsible Party: " + responsible_party + | |
138 | "\nTimestep: " + timestamp + | |
139 | "\nTime before zone refresh (sec): " + refresh_zone_time + | |
140 | "\nTime before retry refresh (sec): " + retry_refresh_time + | |
141 | "\nUpper Limit before Zone is no longer authoritive (sec): " + upper_limit_time + | |
142 | "\nNegative Result TTL: " + negative_result_ttl) | |
143 | ||
144 | self.createAndAddNoteToService( | |
145 | host_id=host_id, | |
146 | service_id=service_id, | |
147 | name="priority", | |
148 | text=text.encode('ascii', 'ignore')) | |
149 | ||
150 | elif result.get(u"type") == u"TXT": # TXT record | |
151 | text = " ".join(result.get(u"data")[:]) | |
152 | self.createAndAddNoteToHost( | |
153 | host_id=host_id, | |
154 | name="TXT Information", | |
155 | text=text.encode('ascii', 'ignore')) | |
156 | ||
157 | except Exception as ex: | |
158 | print("some part of the dig plug-in caused an error! Please check repo/dig/plugin.py") | |
159 | return False | |
160 | ||
161 | ||
162 | return True | |
163 | ||
164 | ||
165 | def createPlugin(): | |
166 | return DigPlugin() | |
167 | ||
168 | if __name__ == "__main__": | |
169 | import sys | |
170 | import os | |
171 | if len(sys.argv) == 2: | |
172 | report_file = sys.argv[1] | |
173 | if os.path.isfile(report_file): | |
174 | plugin = createPlugin() | |
175 | plugin.processReport(report_file) | |
176 | print(plugin.get_json()) | |
177 | else: | |
178 | print(f"Report not found: {report_file}") | |
179 | else: | |
180 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
181 | # 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 | # 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 | from faraday_plugins.plugins.plugin import PluginBase | |
6 | import re | |
7 | import socket | |
8 | ||
9 | __author__ = "Federico Fernandez - @q3rv0" | |
10 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
11 | __license__ = "" | |
12 | __version__ = "1.0.0" | |
13 | __maintainer__ = "Federico Fernandez" | |
14 | __email__ = "[email protected]" | |
15 | __status__ = "Development" | |
16 | ||
17 | ||
18 | class dirbPlugin(PluginBase): | |
19 | ||
20 | def __init__(self): | |
21 | super().__init__() | |
22 | self.id = "dirb" | |
23 | self.name = "Dirb" | |
24 | self.plugin_version = "0.0.1" | |
25 | self.version = "2.22" | |
26 | self.regexpUrl = r'((http[s]?)\:\/\/([\w\.]+)[.\S]+)' | |
27 | self._command_regex = re.compile(r'^(?:sudo dirb|dirb|\.\/dirb|sudo \.\/dirb)\s+(?:(http[s]?)\:\/\/([\w\.]+)[.\S]+)') | |
28 | self.text = [] | |
29 | ||
30 | def getPort(self, host, proto): | |
31 | p = re.search(r"\:([0-9]+)\/", host) | |
32 | if p is not None: | |
33 | return p.group(1) | |
34 | elif proto == 'https': | |
35 | return 443 | |
36 | else: | |
37 | return 80 | |
38 | ||
39 | def getIP(self, host): | |
40 | try: | |
41 | ip = socket.gethostbyname(host) | |
42 | except Exception: | |
43 | pass | |
44 | ||
45 | return ip | |
46 | ||
47 | def state(self, output): | |
48 | if output.find('COULDNT CONNECT') != -1: | |
49 | return "close" | |
50 | else: | |
51 | return "open" | |
52 | ||
53 | def pathsDirListing(self, output): | |
54 | data = [] | |
55 | r = re.findall(self.regexpUrl + r"[\-\._\w\*\s]+\s+\(!\) WARNING: Directory IS LISTABLE", | |
56 | output) | |
57 | for u in r: | |
58 | data.append(u[0]) | |
59 | ||
60 | paths = "\n".join(data) | |
61 | return paths | |
62 | ||
63 | def note(self, output): | |
64 | dirs = re.findall(r"==> DIRECTORY: "+self.regexpUrl, output) | |
65 | files = re.findall(r"\+ " + self.regexpUrl + r" \(.+\)", output) | |
66 | for d in dirs: | |
67 | self.text.append("DIRECTORY: " + d[0]) | |
68 | ||
69 | for f in files: | |
70 | self.text.append("FILE: " + f[0]) | |
71 | ||
72 | self.text = '\n'.join(self.text) | |
73 | ||
74 | def parseOutputString(self, output, debug=False): | |
75 | ||
76 | url = re.search(r"URL_BASE: " + self.regexpUrl, output) | |
77 | paths = self.pathsDirListing(output) | |
78 | status = self.state(output) | |
79 | self.note(output) | |
80 | ||
81 | if output.find('END_TIME') != -1 and url is not None: | |
82 | proto = url.group(2) | |
83 | domain = url.group(3) | |
84 | ip = self.getIP(domain) | |
85 | puerto = self.getPort(url.group(1), proto) | |
86 | ||
87 | host_id = self.createAndAddHost(ip) | |
88 | iface_id = self.createAndAddInterface(host_id, ip, ipv4_address = ip) | |
89 | ||
90 | serv_id = self.createAndAddServiceToInterface(host_id, iface_id, proto, protocol = proto, ports =[puerto], status = status) | |
91 | ||
92 | if len(self.text) > 0: | |
93 | self.createAndAddVulnWebToService(host_id, serv_id, 'Url Fuzzing', severity=0, desc=self.text, website=domain) | |
94 | ||
95 | if len(paths) > 0: | |
96 | self.createAndAddVulnWebToService(host_id, serv_id, "Directory Listing", severity = "med", website = domain, request = paths, method = "GET") | |
97 | ||
98 | return True | |
99 | ||
100 | def processCommandString(self, username, current_path, command_string): | |
101 | """ | |
102 | Adds the -oX parameter to get xml output to the command string that the | |
103 | user has set. | |
104 | """ | |
105 | ||
106 | no_stop_on_warn_msg_re = r"\s+-w" | |
107 | arg_search = re.search(no_stop_on_warn_msg_re,command_string) | |
108 | extra_arg = "" | |
109 | if arg_search is None: | |
110 | extra_arg +=" -w" | |
111 | ||
112 | silent_mode_re = r"\s+-S" | |
113 | arg_search = re.search(silent_mode_re,command_string) | |
114 | if arg_search is None: | |
115 | extra_arg +=" -S" | |
116 | return "%s%s" % (command_string, extra_arg) | |
117 | ||
118 | def createPlugin(): | |
119 | return dirbPlugin() | |
120 | ||
121 | if __name__ == "__main__": | |
122 | import sys | |
123 | import os | |
124 | if len(sys.argv) == 2: | |
125 | report_file = sys.argv[1] | |
126 | if os.path.isfile(report_file): | |
127 | plugin = createPlugin() | |
128 | plugin.processReport(report_file) | |
129 | print(plugin.get_json()) | |
130 | else: | |
131 | print(f"Report not found: {report_file}") | |
132 | else: | |
133 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
134 | # 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 | # 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 json | |
7 | import shlex | |
8 | import socket | |
9 | import argparse | |
10 | import tempfile | |
11 | import urllib.parse as urlparse | |
12 | ||
13 | ||
14 | from faraday_plugins.plugins.plugin import PluginTerminalOutput | |
15 | from faraday_plugins.plugins.plugins_utils import get_vulnweb_url_fields | |
16 | ||
17 | ||
18 | __author__ = "Matías Lang" | |
19 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
20 | __credits__ = ["Matías Lang"] | |
21 | __license__ = "" | |
22 | __version__ = "0.0.1" | |
23 | __maintainer__ = "Matías Lang" | |
24 | __email__ = "[email protected]" | |
25 | __status__ = "Development" | |
26 | ||
27 | ||
28 | status_codes = { | |
29 | 200: "OK", 201: "Created", 202: "Accepted", | |
30 | 203: "Non-Authoritative Information", 204: "No Content", | |
31 | 205: "Reset Content", 206: "Partial Content", 207: "Multi-Status", | |
32 | 208: "Already Reported", 226: "IM Used", 300: "Multiple Choices", | |
33 | 301: "Moved Permanently", 302: "Found", 303: "See Other", | |
34 | 304: "Not Modified", 305: "Use Proxy", 306: "Switch Proxy", | |
35 | 307: "Temporary Redirect", 308: "Permanent Redirect", | |
36 | 400: "Bad Request", 401: "Unauthorized", 402: "Payment Required", | |
37 | 403: "Forbidden", 404: "Not Found", 405: "Method Not Allowed", | |
38 | 406: "Not Acceptable", 407: "Proxy Authentication Required", | |
39 | 408: "Request Timeout", 409: "Conflict", 410: "Gone", | |
40 | 411: "Length Required", 412: "Precondition Failed", | |
41 | 413: "Payload Too Large", 414: "URI Too Long", | |
42 | 415: "Unsupported Media Type", 416: "Range Not Satisfiable", | |
43 | 417: "Expectation Failed", 418: "I'm a teapot", | |
44 | 421: "Misdirected Request", 422: "Unprocessable Entity", 423: "Locked", | |
45 | 424: "Failed Dependency", 426: "Upgrade Required", | |
46 | 428: "Precondition Required", 429: "Too Many Requests", | |
47 | 431: "Request Header Fields Too Large", | |
48 | 451: "Unavailable For Legal Reasons", 500: "Internal Server Error", | |
49 | 501: "Not Implemented", 502: "Bad Gateway", 503: "Service Unavailable", | |
50 | 504: "Gateway Timeout", 505: "HTTP Version Not Supported", | |
51 | 506: "Variant Also Negotiates", 507: "Insufficient Storage", | |
52 | 508: "Loop Detected", 510: "Not Extended", | |
53 | 511: "Network Authentication Required", | |
54 | } | |
55 | ||
56 | ||
57 | class DirsearchPlugin(PluginTerminalOutput): | |
58 | def __init__(self): | |
59 | super().__init__() | |
60 | self.id = "dirsearch" | |
61 | self.name = "dirsearch" | |
62 | self.plugin_version = "0.0.1" | |
63 | self.version = "0.0.1" | |
64 | self._command_regex = re.compile( | |
65 | r'^(sudo )?(python[0-9\.]? )?dirsearch(\.py)?') | |
66 | self.ignore_parsing = False | |
67 | self.json_report_file = None | |
68 | self.addSetting("Ignore 403", str, "1") | |
69 | ||
70 | def parseOutputString(self, output, debug=False): | |
71 | if self.ignore_parsing: | |
72 | return | |
73 | if self.json_report_file: | |
74 | # We ran the plugin via command line | |
75 | try: | |
76 | fp = open(self.json_report_file) | |
77 | except IOError: | |
78 | self.log('Error opening JSON in the file {}'.format( | |
79 | self.json_report_file | |
80 | ), 'ERROR') | |
81 | else: | |
82 | self.parse_json(fp.read()) | |
83 | if self.remove_report: | |
84 | os.unlink(self.json_report_file) | |
85 | else: | |
86 | # We are importing a report | |
87 | self.parse_json(output) | |
88 | ||
89 | def resolve(self, domain): | |
90 | return socket.gethostbyname(domain) | |
91 | ||
92 | @property | |
93 | def should_ignore_403(self): | |
94 | val = self.getSetting('Ignore 403') | |
95 | if not val or not int(val): | |
96 | return False | |
97 | return True | |
98 | ||
99 | def parse_json(self, contents): | |
100 | try: | |
101 | data = json.loads(contents) | |
102 | except ValueError: | |
103 | self.log('Error parsing report. Make sure the file has valid ' | |
104 | 'JSON', 'ERROR') | |
105 | return | |
106 | for (base_url, items) in data.items(): | |
107 | base_split = urlparse.urlsplit(base_url) | |
108 | ip = self.resolve(base_split.hostname) | |
109 | h_id = self.createAndAddHost(ip) | |
110 | ||
111 | i_id = self.createAndAddInterface( | |
112 | h_id, | |
113 | name=ip, | |
114 | ipv4_address=ip, | |
115 | hostname_resolution=[base_split.hostname]) | |
116 | ||
117 | s_id = self.createAndAddServiceToInterface( | |
118 | h_id, | |
119 | i_id, | |
120 | base_split.scheme, | |
121 | 'tcp', | |
122 | [base_split.port], | |
123 | status="open") | |
124 | ||
125 | for item in items: | |
126 | self.parse_found_url(base_url, h_id, s_id, item) | |
127 | ||
128 | def parse_found_url(self, base_url, h_id, s_id, item): | |
129 | if self.should_ignore_403 and item['status'] == 403: | |
130 | return | |
131 | url = urlparse.urlsplit(urlparse.urljoin(base_url, item['path'])) | |
132 | response = "HTTP/1.1 {} {}\nContent-Length: {}".format( | |
133 | item['status'], status_codes.get(item['status'], 'unknown'), | |
134 | item['content-length']) | |
135 | redirect = item.get('redirect') | |
136 | if redirect is not None: | |
137 | response += '\nLocation: {}'.format(redirect) | |
138 | self.createAndAddVulnWebToService( | |
139 | h_id, | |
140 | s_id, | |
141 | name='Path found: {} ({})'.format(item['path'], item['status']), | |
142 | desc="Dirsearch tool found the following URL: {}".format( | |
143 | url.geturl()), | |
144 | severity="info", | |
145 | method='GET', | |
146 | response=response, | |
147 | **get_vulnweb_url_fields(url.geturl())) | |
148 | ||
149 | def processCommandString(self, username, current_path, command_string): | |
150 | parser = argparse.ArgumentParser(conflict_handler='resolve') | |
151 | parser.add_argument('-h', '--help', action='store_true') | |
152 | parser.add_argument('--json-report') | |
153 | args, unknown = parser.parse_known_args(shlex.split(command_string)) | |
154 | ||
155 | if args.help: | |
156 | self.devlog('help detected, ignoring parsing') | |
157 | return command_string | |
158 | if args.json_report: | |
159 | # The user already defined a path to the JSON report | |
160 | self.json_report_file = args.json_report | |
161 | self.remove_report = False | |
162 | return command_string | |
163 | else: | |
164 | # Use temporal file to save the report data | |
165 | self.json_report_file = tempfile.mktemp( | |
166 | prefix="dirsearch_report_", suffix=".json") | |
167 | self.devlog('Setting report file to {}'.format( | |
168 | self.json_report_file)) | |
169 | self.remove_report = True | |
170 | return '{} --json-report {}'.format(command_string, | |
171 | self.json_report_file) | |
172 | ||
173 | ||
174 | def createPlugin(): | |
175 | return DirsearchPlugin() | |
176 | ||
177 | if __name__ == "__main__": | |
178 | import sys | |
179 | import os | |
180 | if len(sys.argv) == 2: | |
181 | report_file = sys.argv[1] | |
182 | if os.path.isfile(report_file): | |
183 | plugin = createPlugin() | |
184 | plugin.processReport(report_file) | |
185 | print(plugin.get_json()) | |
186 | else: | |
187 | print(f"Report not found: {report_file}") | |
188 | else: | |
189 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
190 | # 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 | # 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 | ||
10 | try: | |
11 | import xml.etree.cElementTree as ET | |
12 | import xml.etree.ElementTree as ET_ORIG | |
13 | ETREE_VERSION = ET_ORIG.VERSION | |
14 | except ImportError: | |
15 | import xml.etree.ElementTree as ET | |
16 | ETREE_VERSION = ET.VERSION | |
17 | ||
18 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] | |
19 | ||
20 | current_path = os.path.abspath(os.getcwd()) | |
21 | ||
22 | __author__ = "Francisco Amato" | |
23 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
24 | __credits__ = ["Francisco Amato"] | |
25 | __license__ = "" | |
26 | __version__ = "1.0.0" | |
27 | __maintainer__ = "Francisco Amato" | |
28 | __email__ = "[email protected]" | |
29 | __status__ = "Development" | |
30 | ||
31 | ||
32 | class DnsenumXmlParser: | |
33 | """ | |
34 | The objective of this class is to parse an xml file generated by the dnsenum tool. | |
35 | ||
36 | TODO: Handle errors. | |
37 | TODO: Test dnsenum output version. Handle what happens if the parser doesn't support it. | |
38 | TODO: Test cases. | |
39 | ||
40 | @param dnsenum_xml_filepath A proper xml generated by dnsenum | |
41 | """ | |
42 | ||
43 | def __init__(self, xml_output): | |
44 | tree = self.parse_xml(xml_output) | |
45 | ||
46 | if tree: | |
47 | self.items = [data for data in self.get_items(tree)] | |
48 | else: | |
49 | self.items = [] | |
50 | ||
51 | def parse_xml(self, xml_output): | |
52 | """ | |
53 | Open and parse an xml file. | |
54 | ||
55 | TODO: Write custom parser to just read the nodes that we need instead of | |
56 | reading the whole file. | |
57 | ||
58 | @return xml_tree An xml tree instance. None if error. | |
59 | """ | |
60 | try: | |
61 | tree = ET.fromstring(xml_output) | |
62 | except SyntaxError as err: | |
63 | print("SyntaxError: %s. %s" % (err, xml_output)) | |
64 | return None | |
65 | ||
66 | return tree | |
67 | ||
68 | def get_items(self, tree): | |
69 | """ | |
70 | @return items A list of Host instances | |
71 | """ | |
72 | bugtype = '' | |
73 | ||
74 | node = tree.findall('testdata')[0] | |
75 | for hostnode in node.findall('host'): | |
76 | yield Item(hostnode) | |
77 | ||
78 | ||
79 | def get_attrib_from_subnode(xml_node, subnode_xpath_expr, attrib_name): | |
80 | """ | |
81 | Finds a subnode in the item node and the retrieves a value from it | |
82 | ||
83 | @return An attribute value | |
84 | """ | |
85 | global ETREE_VERSION | |
86 | node = None | |
87 | ||
88 | if ETREE_VERSION[0] <= 1 and ETREE_VERSION[1] < 3: | |
89 | ||
90 | match_obj = re.search( | |
91 | "([^\@]+?)\[\@([^=]*?)=\'([^\']*?)\'", subnode_xpath_expr) | |
92 | if match_obj is not None: | |
93 | node_to_find = match_obj.group(1) | |
94 | xpath_attrib = match_obj.group(2) | |
95 | xpath_value = match_obj.group(3) | |
96 | for node_found in xml_node.findall(node_to_find): | |
97 | if node_found.attrib[xpath_attrib] == xpath_value: | |
98 | node = node_found | |
99 | break | |
100 | else: | |
101 | node = xml_node.find(subnode_xpath_expr) | |
102 | ||
103 | else: | |
104 | node = xml_node.find(subnode_xpath_expr) | |
105 | ||
106 | if node is not None: | |
107 | return node.get(attrib_name) | |
108 | ||
109 | return None | |
110 | ||
111 | ||
112 | class Item: | |
113 | """ | |
114 | An abstract representation of a Item | |
115 | ||
116 | TODO: Consider evaluating the attributes lazily | |
117 | TODO: Write what's expected to be present in the nodes | |
118 | TODO: Refactor both Host and the Port clases? | |
119 | ||
120 | @param item_node A item_node taken from an dnsenum xml tree | |
121 | """ | |
122 | ||
123 | def __init__(self, item_node): | |
124 | self.node = item_node | |
125 | ||
126 | self.hostname = self.get_text_from_subnode('hostname') | |
127 | self.ip = self.node.text | |
128 | ||
129 | def do_clean(self, value): | |
130 | myreturn = "" | |
131 | if value is not None: | |
132 | myreturn = re.sub("\n", "", value) | |
133 | return myreturn | |
134 | ||
135 | def get_text_from_subnode(self, subnode_xpath_expr): | |
136 | """ | |
137 | Finds a subnode in the host node and the retrieves a value from it. | |
138 | ||
139 | @return An attribute value | |
140 | """ | |
141 | sub_node = self.node.find(subnode_xpath_expr) | |
142 | if sub_node is not None: | |
143 | return sub_node.text | |
144 | ||
145 | return None | |
146 | ||
147 | ||
148 | class DnsenumPlugin(PluginBase): | |
149 | """ | |
150 | Example plugin to parse dnsenum output. | |
151 | """ | |
152 | ||
153 | def __init__(self): | |
154 | super().__init__() | |
155 | self.id = "Dnsenum" | |
156 | self.name = "Dnsenum XML Output Plugin" | |
157 | self.plugin_version = "0.0.1" | |
158 | self.version = "1.2.2" | |
159 | self.options = None | |
160 | self._current_output = None | |
161 | self._command_regex = re.compile( | |
162 | r'^(sudo dnsenum|dnsenum|sudo dnsenum\.pl|dnsenum\.pl|perl dnsenum\.pl|\.\/dnsenum\.pl).*?') | |
163 | ||
164 | ||
165 | def parseOutputString(self, output, debug=False): | |
166 | """ | |
167 | This method will discard the output the shell sends, it will read it from | |
168 | the xml where it expects it to be present. | |
169 | ||
170 | NOTE: if 'debug' is true then it is being run from a test case and the | |
171 | output being sent is valid. | |
172 | """ | |
173 | ||
174 | parser = DnsenumXmlParser(output) | |
175 | ||
176 | for item in parser.items: | |
177 | h_id = self.createAndAddHost(item.ip) | |
178 | i_id = self.createAndAddInterface( | |
179 | h_id, | |
180 | item.ip, | |
181 | ipv4_address=item.ip, | |
182 | hostname_resolution=[item.hostname]) | |
183 | ||
184 | del parser | |
185 | ||
186 | xml_arg_re = re.compile(r"^.*(-o\s*[^\s]+).*$") | |
187 | ||
188 | def processCommandString(self, username, current_path, command_string): | |
189 | """ | |
190 | Adds the -oX parameter to get xml output to the command string that the | |
191 | user has set. | |
192 | """ | |
193 | ||
194 | arg_match = self.xml_arg_re.match(command_string) | |
195 | ||
196 | 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) | |
201 | else: | |
202 | return re.sub(arg_match.group(1), | |
203 | r"-o %s" % self._output_file_path, | |
204 | command_string) | |
205 | ||
206 | def setHost(self): | |
207 | pass | |
208 | ||
209 | ||
210 | def createPlugin(): | |
211 | return DnsenumPlugin() | |
212 | ||
213 | if __name__ == "__main__": | |
214 | import sys | |
215 | import os | |
216 | if len(sys.argv) == 2: | |
217 | report_file = sys.argv[1] | |
218 | if os.path.isfile(report_file): | |
219 | plugin = createPlugin() | |
220 | plugin.processReport(report_file) | |
221 | print(plugin.get_json()) | |
222 | else: | |
223 | print(f"Report not found: {report_file}") | |
224 | else: | |
225 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
226 | # 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 | # I'm Py3⏎ |
0 | """from __future__ import print_function | |
1 | ||
2 | Faraday Penetration Test IDE | |
3 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
4 | See the file 'doc/LICENSE' for the license information | |
5 | """ | |
6 | from faraday_plugins.plugins.plugin import PluginBase | |
7 | import re | |
8 | import os | |
9 | import random | |
10 | from collections import defaultdict | |
11 | ||
12 | current_path = os.path.abspath(os.getcwd()) | |
13 | ||
14 | __author__ = "Francisco Amato" | |
15 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
16 | __credits__ = ["Francisco Amato"] | |
17 | __license__ = "" | |
18 | __version__ = "1.0.0" | |
19 | __maintainer__ = "Francisco Amato" | |
20 | __email__ = "[email protected]" | |
21 | __status__ = "Development" | |
22 | ||
23 | ||
24 | class DnsmapParser: | |
25 | """ | |
26 | The objective of this class is to parse an xml file generated by the | |
27 | dnsmap tool. | |
28 | ||
29 | TODO: Handle errors. | |
30 | TODO: Test dnsmap output version. Handle what happens if the parser | |
31 | doesn't support it. | |
32 | TODO: Test cases. | |
33 | ||
34 | @param dnsmap_filepath A proper simple report generated by dnsmap | |
35 | """ | |
36 | ||
37 | def __init__(self, output): | |
38 | self.items = defaultdict(list) | |
39 | if "\n\n" in output: | |
40 | self.parse_txt(output) | |
41 | else: | |
42 | self.parse_csv(output) | |
43 | ||
44 | def parse_txt(self, output): | |
45 | hosts = self.split_output_lines(output) | |
46 | ||
47 | for host_data in hosts: | |
48 | if len(host_data) == 2: | |
49 | ip = self.clean_ip(host_data[1]) | |
50 | hostname = host_data[0] | |
51 | self.add_host_info_to_items(ip, hostname) | |
52 | elif len(host_data) > 2: | |
53 | hostname = host_data.pop(0) | |
54 | for ip_address in host_data: | |
55 | ip = self.clean_ip(ip_address) | |
56 | self.add_host_info_to_items(ip, hostname) | |
57 | ||
58 | def parse_csv(self, output): | |
59 | hosts = list(filter(None, output.splitlines())) | |
60 | ||
61 | for host in hosts: | |
62 | host_data = host.split(",", 1) | |
63 | if host_data[1].count(',') == 0: | |
64 | ip = host_data[1] | |
65 | hostname = host_data[0] | |
66 | self.add_host_info_to_items(ip, hostname) | |
67 | else: | |
68 | hostname = host_data.pop(0) | |
69 | ips = host_data[0].split(",") | |
70 | for ip_address in ips: | |
71 | self.add_host_info_to_items(ip_address, hostname) | |
72 | ||
73 | def split_output_lines(self, output): | |
74 | splitted = output.splitlines() | |
75 | hosts_list = [] | |
76 | aux_list = [] | |
77 | for i in range(0, len(splitted)): | |
78 | if not splitted[i]: | |
79 | hosts_list.append(aux_list) | |
80 | aux_list = [] | |
81 | continue | |
82 | else: | |
83 | aux_list.append(splitted[i]) | |
84 | return hosts_list | |
85 | ||
86 | def clean_ip(self, item): | |
87 | ip = item.split(':', 1) | |
88 | return ip[1].strip() | |
89 | ||
90 | def add_host_info_to_items(self, ip_address, hostname): | |
91 | self.items[ip_address].append(hostname) | |
92 | ||
93 | ||
94 | class DnsmapPlugin(PluginBase): | |
95 | """Example plugin to parse dnsmap output.""" | |
96 | ||
97 | def __init__(self): | |
98 | super().__init__() | |
99 | self.id = "Dnsmap" | |
100 | self.name = "Dnsmap Output Plugin" | |
101 | self.plugin_version = "0.3" | |
102 | self.version = "0.30" | |
103 | self.options = None | |
104 | self._current_output = None | |
105 | self.current_path = None | |
106 | self._command_regex = re.compile(r'^(sudo dnsmap|dnsmap|\.\/dnsmap).*?') | |
107 | self.xml_arg_re = re.compile(r"^.*(-r\s*[^\s]+).*$") | |
108 | ||
109 | ||
110 | def canParseCommandString(self, current_input): | |
111 | if self._command_regex.match(current_input.strip()): | |
112 | return True | |
113 | else: | |
114 | return False | |
115 | ||
116 | def parseOutputString(self, output, debug=False): | |
117 | """ | |
118 | This method will discard the output the shell sends, it will read it | |
119 | from the xml where it expects it to be present. | |
120 | """ | |
121 | parser = DnsmapParser(output) | |
122 | for ip_address, hostnames in parser.items.items(): | |
123 | h_id = self.createAndAddHost(ip_address, hostnames=hostnames) | |
124 | return True | |
125 | ||
126 | def processCommandString(self, username, current_path, command_string): | |
127 | """ | |
128 | Adds the parameter to get output to the command string that the | |
129 | user has set. | |
130 | """ | |
131 | arg_match = self.xml_arg_re.match(command_string) | |
132 | ||
133 | if arg_match is None: | |
134 | return "%s -r %s \\n" % (command_string, self._output_file_path) | |
135 | else: | |
136 | return re.sub(arg_match.group(1), | |
137 | r"-r %s" % self._output_file_path, | |
138 | command_string) | |
139 | ||
140 | ||
141 | def createPlugin(): | |
142 | return DnsmapPlugin() | |
143 | ||
144 | if __name__ == "__main__": | |
145 | import sys | |
146 | import os | |
147 | if len(sys.argv) == 2: | |
148 | report_file = sys.argv[1] | |
149 | if os.path.isfile(report_file): | |
150 | plugin = createPlugin() | |
151 | plugin.processReport(report_file) | |
152 | print(plugin.get_json()) | |
153 | else: | |
154 | print(f"Report not found: {report_file}") | |
155 | else: | |
156 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
157 | # 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 | # I'm Py3⏎ |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | """ | |
4 | Faraday Penetration Test IDE | |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
6 | See the file 'doc/LICENSE' for the license information | |
7 | """ | |
8 | from faraday_plugins.plugins.plugin import PluginBase | |
9 | import re | |
10 | import os | |
11 | import sys | |
12 | ||
13 | try: | |
14 | import xml.etree.cElementTree as ET | |
15 | import xml.etree.ElementTree as ET_ORIG | |
16 | ETREE_VERSION = ET_ORIG.VERSION | |
17 | except ImportError: | |
18 | import xml.etree.ElementTree as ET | |
19 | ETREE_VERSION = ET.VERSION | |
20 | ||
21 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] | |
22 | ||
23 | current_path = os.path.abspath(os.getcwd()) | |
24 | ||
25 | __author__ = "Francisco Amato" | |
26 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
27 | __credits__ = ["Francisco Amato"] | |
28 | __license__ = "" | |
29 | __version__ = "1.0.0" | |
30 | __maintainer__ = "Francisco Amato" | |
31 | __email__ = "[email protected]" | |
32 | __status__ = "Development" | |
33 | ||
34 | ||
35 | class DnsreconXmlParser: | |
36 | """ | |
37 | The objective of this class is to parse an xml file generated by the dnsrecon tool. | |
38 | ||
39 | TODO: Handle errors. | |
40 | TODO: Test dnsrecon output version. Handle what happens if the parser doesn't support it. | |
41 | TODO: Test cases. | |
42 | ||
43 | @param dnsrecon_xml_filepath A proper xml generated by dnsrecon | |
44 | """ | |
45 | ||
46 | def __init__(self, xml_output): | |
47 | ||
48 | tree = self.parse_xml(xml_output) | |
49 | ||
50 | if tree: | |
51 | self.hosts = [host for host in self.get_hosts(tree)] | |
52 | else: | |
53 | self.hosts = [] | |
54 | ||
55 | def parse_xml(self, xml_output): | |
56 | """ | |
57 | Open and parse an xml file. | |
58 | ||
59 | TODO: Write custom parser to just read the nodes that we need instead of | |
60 | reading the whole file. | |
61 | ||
62 | @return xml_tree An xml tree instance. None if error. | |
63 | """ | |
64 | try: | |
65 | tree = ET.fromstring(xml_output) | |
66 | except SyntaxError as err: | |
67 | print("SyntaxError: %s. %s" % (err, xml_output)) | |
68 | return None | |
69 | ||
70 | return tree | |
71 | ||
72 | def get_hosts(self, tree): | |
73 | """ | |
74 | @return items A list of Host instances | |
75 | """ | |
76 | for item_node in tree.findall('record'): | |
77 | yield Item(item_node) | |
78 | ||
79 | ||
80 | def get_attrib_from_subnode(xml_node, subnode_xpath_expr, attrib_name): | |
81 | """ | |
82 | Finds a subnode in the item node and the retrieves a value from it | |
83 | ||
84 | @return An attribute value | |
85 | """ | |
86 | global ETREE_VERSION | |
87 | node = None | |
88 | ||
89 | if ETREE_VERSION[0] <= 1 and ETREE_VERSION[1] < 3: | |
90 | ||
91 | match_obj = re.search( | |
92 | "([^\@]+?)\[\@([^=]*?)=\'([^\']*?)\'", subnode_xpath_expr) | |
93 | if match_obj is not None: | |
94 | ||
95 | node_to_find = match_obj.group(1) | |
96 | xpath_attrib = match_obj.group(2) | |
97 | xpath_value = match_obj.group(3) | |
98 | for node_found in xml_node.findall(node_to_find): | |
99 | if node_found.attrib[xpath_attrib] == xpath_value: | |
100 | node = node_found | |
101 | break | |
102 | else: | |
103 | node = xml_node.find(subnode_xpath_expr) | |
104 | ||
105 | else: | |
106 | node = xml_node.find(subnode_xpath_expr) | |
107 | ||
108 | if node is not None: | |
109 | return node.get(attrib_name) | |
110 | ||
111 | return None | |
112 | ||
113 | ||
114 | class Item: | |
115 | """ | |
116 | An abstract representation of a Item | |
117 | ||
118 | TODO: Consider evaluating the attributes lazily | |
119 | TODO: Write what's expected to be present in the nodes | |
120 | TODO: Refactor both Host and the Port clases? | |
121 | ||
122 | @param item_node A item_node taken from an dnsrecon xml tree | |
123 | """ | |
124 | ||
125 | def __init__(self, item_node): | |
126 | self.node = item_node | |
127 | ||
128 | self.type = self.do_clean(self.node.get('type')) | |
129 | self.zonetransfer = self.do_clean(self.node.get('zone_transfer')) | |
130 | self.ns_server = self.do_clean(self.node.get('ns_server')) | |
131 | self.address = self.do_clean(self.node.get( | |
132 | 'address')) if not self.type == "info" else self.ns_server | |
133 | ||
134 | self.target = self.do_clean(self.node.get('target')) | |
135 | self.name = self.do_clean(self.node.get('name')) | |
136 | self.exchange = self.do_clean(self.node.get('exchange')) | |
137 | ||
138 | print("GENERATION:" + self.type, self.address, self.zonetransfer) | |
139 | ||
140 | def do_clean(self, value): | |
141 | myreturn = '' | |
142 | if value is not None: | |
143 | myreturn = re.sub(" |\n", "", value) | |
144 | return myreturn | |
145 | ||
146 | def get_text_from_subnode(self, subnode_xpath_expr): | |
147 | """ | |
148 | Finds a subnode in the host node and the retrieves a value from it. | |
149 | ||
150 | @return An attribute value | |
151 | """ | |
152 | sub_node = self.node.find(subnode_xpath_expr) | |
153 | if sub_node is not None: | |
154 | return sub_node.text | |
155 | ||
156 | return None | |
157 | ||
158 | ||
159 | class DnsreconPlugin(PluginBase): | |
160 | """ | |
161 | Example plugin to parse dnsrecon output. | |
162 | """ | |
163 | ||
164 | def __init__(self): | |
165 | super().__init__() | |
166 | self.id = "Dnsrecon" | |
167 | self.name = "Dnsrecon XML Output Plugin" | |
168 | self.plugin_version = "0.0.2" | |
169 | self.version = "0.8.7" | |
170 | self.framework_version = "1.0.0" | |
171 | self.options = None | |
172 | self._current_output = None | |
173 | self._command_regex = re.compile( | |
174 | r'^(sudo dnsrecon|dnsrecon|sudo dnsrecon\.py|dnsrecon\.py|python dnsrecon\.py|\.\/dnsrecon\.py).*?') | |
175 | ||
176 | ||
177 | def validHosts(self, hosts): | |
178 | valid_records = ["NS", "CNAME", "A", "MX", "info"] | |
179 | hosts = list(filter(lambda h: h.type in valid_records, hosts)) | |
180 | return hosts | |
181 | ||
182 | def parseOutputString(self, output, debug=False): | |
183 | """ | |
184 | This method will discard the output the shell sends, it will read it from | |
185 | the xml where it expects it to be present. | |
186 | ||
187 | NOTE: if 'debug' is true then it is being run from a test case and the | |
188 | output being sent is valid. | |
189 | """ | |
190 | ||
191 | parser = DnsreconXmlParser(output) | |
192 | ||
193 | for host in self.validHosts(parser.hosts): | |
194 | ||
195 | print(host.type, host.name, host.zonetransfer) | |
196 | hostname = host.target | |
197 | if host.type == "MX": | |
198 | hostname = host.exchange | |
199 | elif host.type == "A": | |
200 | hostname = host.name | |
201 | ||
202 | h_id = self.createAndAddHost(host.address) | |
203 | ||
204 | if self._isIPV4(str(host.address)): | |
205 | i_id = self.createAndAddInterface( | |
206 | h_id, | |
207 | name=host.address, | |
208 | ipv4_address=host.address, | |
209 | hostname_resolution=[hostname]) | |
210 | else: | |
211 | i_id = self.createAndAddInterface( | |
212 | h_id, | |
213 | name=host.address, | |
214 | ipv6_address=host.address, | |
215 | hostname_resolution=[hostname]) | |
216 | ||
217 | if host.type == "info": | |
218 | ||
219 | s_id = self.createAndAddServiceToInterface( | |
220 | h_id, | |
221 | i_id, | |
222 | "domain", | |
223 | protocol="tcp", | |
224 | ports=["53"], | |
225 | status="open") | |
226 | ||
227 | if host.zonetransfer == "success": | |
228 | v_id = self.createAndAddVulnToService( | |
229 | h_id, | |
230 | s_id, | |
231 | name="Zone transfer", | |
232 | desc="A Dns server allows unrestricted zone transfers", | |
233 | ref=["CVE-1999-0532"]) | |
234 | ||
235 | del parser | |
236 | ||
237 | def _isIPV4(self, ip): | |
238 | if len(ip.split(".")) == 4: | |
239 | return True | |
240 | else: | |
241 | return False | |
242 | ||
243 | xml_arg_re = re.compile(r"^.*(--xml\s*[^\s]+).*$") | |
244 | ||
245 | def processCommandString(self, username, current_path, command_string): | |
246 | """ | |
247 | Adds the -oX parameter to get xml output to the command string that the | |
248 | user has set. | |
249 | """ | |
250 | arg_match = self.xml_arg_re.match(command_string) | |
251 | ||
252 | if arg_match is None: | |
253 | return re.sub(r"(^.*?dnsrecon(\.py)?)", | |
254 | r"\1 --xml %s" % self._output_file_path, | |
255 | command_string) | |
256 | else: | |
257 | return re.sub(arg_match.group(1), | |
258 | r"--xml %s" % self._output_file_path, | |
259 | command_string) | |
260 | ||
261 | def setHost(self): | |
262 | pass | |
263 | ||
264 | ||
265 | def createPlugin(): | |
266 | return DnsreconPlugin() | |
267 | ||
268 | if __name__ == "__main__": | |
269 | import sys | |
270 | import os | |
271 | if len(sys.argv) == 2: | |
272 | report_file = sys.argv[1] | |
273 | if os.path.isfile(report_file): | |
274 | plugin = createPlugin() | |
275 | plugin.processReport(report_file) | |
276 | print(plugin.get_json()) | |
277 | else: | |
278 | print(f"Report not found: {report_file}") | |
279 | else: | |
280 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
281 | ||
282 | # 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 | # 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 PluginBase | |
8 | import re | |
9 | import os | |
10 | import socket | |
11 | ||
12 | current_path = os.path.abspath(os.getcwd()) | |
13 | ||
14 | __author__ = "Francisco Amato" | |
15 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
16 | __credits__ = ["Francisco Amato"] | |
17 | __license__ = "" | |
18 | __version__ = "1.0.0" | |
19 | __maintainer__ = "Francisco Amato" | |
20 | __email__ = "[email protected]" | |
21 | __status__ = "Development" | |
22 | ||
23 | ||
24 | class DnswalkParser: | |
25 | """ | |
26 | The objective of this class is to parse an xml file generated | |
27 | by the dnswalk tool. | |
28 | ||
29 | TODO: Handle errors. | |
30 | TODO: Test dnswalk output version. Handle what happens if the parser | |
31 | doesn't support it. | |
32 | TODO: Test cases. | |
33 | ||
34 | @param dnswalk_filepath A proper simple report generated by dnswalk | |
35 | """ | |
36 | ||
37 | def __init__(self, output): | |
38 | ||
39 | lists = output.split("\n") | |
40 | self.items = [] | |
41 | ||
42 | for line in lists: | |
43 | mregex = re.search("WARN: ([\w\.]+) ([\w]+) ([\w\.]+):", line) | |
44 | if mregex is not None: | |
45 | ||
46 | item = { | |
47 | 'host': mregex.group(1), | |
48 | 'ip': mregex.group(3), | |
49 | 'type': mregex.group(2)} | |
50 | ||
51 | self.items.append(item) | |
52 | ||
53 | mregex = re.search( | |
54 | "Getting zone transfer of ([\w\.]+) from ([\w\.]+)\.\.\.done\.", | |
55 | line) | |
56 | ||
57 | if mregex is not None: | |
58 | ip = self.getAddress(mregex.group(2)) | |
59 | item = { | |
60 | 'host': mregex.group(1), | |
61 | 'ip': ip, | |
62 | 'type': 'info'} | |
63 | self.items.append(item) | |
64 | ||
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 | ||
72 | ||
73 | class DnswalkPlugin(PluginBase): | |
74 | """ | |
75 | Example plugin to parse dnswalk output. | |
76 | """ | |
77 | ||
78 | def __init__(self): | |
79 | super().__init__() | |
80 | self.id = "Dnswalk" | |
81 | self.name = "Dnswalk XML Output Plugin" | |
82 | self.plugin_version = "0.0.1" | |
83 | self.version = "2.0.2" | |
84 | self.options = None | |
85 | self._current_output = None | |
86 | self._current_path = None | |
87 | self._command_regex = re.compile( | |
88 | r'^(sudo dnswalk|dnswalk|\.\/dnswalk).*?') | |
89 | ||
90 | global current_path | |
91 | ||
92 | def canParseCommandString(self, current_input): | |
93 | if self._command_regex.match(current_input.strip()): | |
94 | return True | |
95 | else: | |
96 | return False | |
97 | ||
98 | def parseOutputString(self, output, debug=False): | |
99 | """ | |
100 | output is the shell output of command Dnswalk. | |
101 | """ | |
102 | parser = DnswalkParser(output) | |
103 | ||
104 | for item in parser.items: | |
105 | ||
106 | if item['type'] == "A": | |
107 | ||
108 | h_id = self.createAndAddHost(item['ip']) | |
109 | i_id = self.createAndAddInterface( | |
110 | h_id, | |
111 | item['ip'], | |
112 | ipv4_address=item['ip'], | |
113 | hostname_resolution=[item['host']]) | |
114 | ||
115 | elif item['type'] == "info": | |
116 | ||
117 | h_id = self.createAndAddHost(item['ip']) | |
118 | ||
119 | i_id = self.createAndAddInterface( | |
120 | h_id, | |
121 | item['ip'], | |
122 | ipv4_address=item['ip'], | |
123 | hostname_resolution=[item['host']]) | |
124 | ||
125 | s_id = self.createAndAddServiceToInterface( | |
126 | h_id, | |
127 | i_id, | |
128 | "domain", | |
129 | "tcp", | |
130 | ports=['53']) | |
131 | ||
132 | self.createAndAddVulnToService( | |
133 | h_id, | |
134 | s_id, | |
135 | "Zone transfer", | |
136 | desc="A Dns server allows unrestricted zone transfers", | |
137 | ref=["CVE-1999-0532"]) | |
138 | ||
139 | return True | |
140 | ||
141 | def processCommandString(self, username, current_path, command_string): | |
142 | return None | |
143 | ||
144 | ||
145 | def createPlugin(): | |
146 | return DnswalkPlugin() | |
147 | ||
148 | if __name__ == "__main__": | |
149 | import sys | |
150 | import os | |
151 | if len(sys.argv) == 2: | |
152 | report_file = sys.argv[1] | |
153 | if os.path.isfile(report_file): | |
154 | plugin = createPlugin() | |
155 | plugin.processReport(report_file) | |
156 | print(plugin.get_json()) | |
157 | else: | |
158 | print(f"Report not found: {report_file}") | |
159 | else: | |
160 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
161 | # 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 | # 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 socket | |
8 | import re | |
9 | import os | |
10 | import random | |
11 | ||
12 | current_path = os.path.abspath(os.getcwd()) | |
13 | ||
14 | __author__ = "Francisco Amato" | |
15 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
16 | __credits__ = ["Francisco Amato"] | |
17 | __license__ = "" | |
18 | __version__ = "1.0.0" | |
19 | __maintainer__ = "Francisco Amato" | |
20 | __email__ = "[email protected]" | |
21 | __status__ = "Development" | |
22 | ||
23 | valid_records = ["NS", "CNAME", "A"] | |
24 | ||
25 | ||
26 | class FierceParser: | |
27 | """ | |
28 | The objective of this class is to parse an shell output generated by | |
29 | the fierce tool. | |
30 | ||
31 | TODO: Handle errors. | |
32 | TODO: Test fierce output version. Handle what happens if the parser | |
33 | doesn't support it. | |
34 | TODO: Test cases. | |
35 | ||
36 | @param fierce_filepath A proper simple report generated by fierce | |
37 | """ | |
38 | ||
39 | def __init__(self, output): | |
40 | self.target = None | |
41 | self.items = [] | |
42 | ||
43 | regex = re.search( | |
44 | "DNS Servers for ([\w\.-]+):\n([^$]+)Trying zone transfer first...", | |
45 | output) | |
46 | ||
47 | if regex is not None: | |
48 | self.target = regex.group(1) | |
49 | mstr = re.sub("\t", "", regex.group(2)) | |
50 | self.dns = list(filter(None, mstr.splitlines())) | |
51 | ||
52 | regex = re.search( | |
53 | "Now performing [\d]+ test\(s\)...\n([^$]+)\nSubnets found ", | |
54 | output) | |
55 | if regex is not None: | |
56 | hosts_list = regex.group(1).splitlines() | |
57 | for i in hosts_list: | |
58 | if i != "": | |
59 | mstr = i.split("\t") | |
60 | host = mstr[1] | |
61 | record = "A" | |
62 | ip = mstr[0] | |
63 | self.add_host_info_to_items(ip, host, record) | |
64 | ||
65 | self.isZoneVuln = False | |
66 | output = output.replace('\\$', '') | |
67 | regex = re.search( | |
68 | "Whoah, it worked - misconfigured DNS server found:([^$]+)\nThere isn't much point continuing, you have everything.", output) | |
69 | ||
70 | if regex is not None: | |
71 | self.isZoneVuln = True | |
72 | dns_list = regex.group(1).splitlines() | |
73 | for i in dns_list: | |
74 | if i != "": | |
75 | mstr = i.split() | |
76 | if (mstr and mstr[0] != "" and len(mstr) > 3 and mstr[3] in valid_records): | |
77 | host = mstr[0] | |
78 | record = mstr[3] | |
79 | ip = mstr[4] | |
80 | self.add_host_info_to_items(ip, host, record) | |
81 | ||
82 | def add_host_info_to_items(self, ip_address, hostname, record): | |
83 | data = {} | |
84 | exists = False | |
85 | for item in self.items: | |
86 | if ip_address in item['ip']: | |
87 | item['hosts'].append(hostname) | |
88 | exists = True | |
89 | ||
90 | if not exists: | |
91 | data['ip'] = ip_address | |
92 | data['hosts'] = [hostname] | |
93 | data['record'] = record | |
94 | self.items.append(data) | |
95 | ||
96 | ||
97 | class FiercePlugin(PluginBase): | |
98 | """ | |
99 | Example plugin to parse fierce output. | |
100 | """ | |
101 | ||
102 | def __init__(self): | |
103 | super().__init__() | |
104 | self.id = "Fierce" | |
105 | self.name = "Fierce Output Plugin" | |
106 | self.plugin_version = "0.0.1" | |
107 | self.version = "0.9.9" | |
108 | self.options = None | |
109 | self._current_output = None | |
110 | self._current_path = None | |
111 | self._command_regex = re.compile( | |
112 | r'^(sudo fierce|fierce|sudo fierce\.pl|fierce\.pl|perl fierce\.pl|\.\/fierce\.pl).*?') | |
113 | global current_path | |
114 | ||
115 | self.xml_arg_re = re.compile(r"^.*(>\s*[^\s]+).*$") | |
116 | ||
117 | def canParseCommandString(self, current_input): | |
118 | if self._command_regex.match(current_input.strip()): | |
119 | return True | |
120 | else: | |
121 | return False | |
122 | ||
123 | def resolveCNAME(self, item, items): | |
124 | for i in items: | |
125 | if (item['ip'] in i['hosts']): | |
126 | item['ip'] = i['ip'] | |
127 | return item | |
128 | try: | |
129 | item['ip'] = socket.gethostbyname(item['ip']) | |
130 | except: | |
131 | pass | |
132 | return item | |
133 | ||
134 | def resolveNS(self, item, items): | |
135 | try: | |
136 | item['hosts'][0] = item['ip'] | |
137 | item['ip'] = socket.gethostbyname(item['ip']) | |
138 | except: | |
139 | pass | |
140 | return item | |
141 | ||
142 | def parseOutputString(self, output, debug=False): | |
143 | ||
144 | parser = FierceParser(output) | |
145 | for item in parser.items: | |
146 | ||
147 | item['isResolver'] = False | |
148 | item['isZoneVuln'] = False | |
149 | if (item['record'] == "CNAME"): | |
150 | self.resolveCNAME(item, parser.items) | |
151 | if (item['record'] == "NS"): | |
152 | self.resolveNS(item, parser.items) | |
153 | item['isResolver'] = True | |
154 | item['isZoneVuln'] = parser.isZoneVuln | |
155 | for item2 in parser.items: | |
156 | ||
157 | if item['ip'] == item2['ip'] and item != item2: | |
158 | item2['isResolver'] = item['isResolver'] | |
159 | item2['isZoneVuln'] = item['isZoneVuln'] | |
160 | item['ip'] = '' | |
161 | ||
162 | for item in parser.items: | |
163 | if item['ip'] == "127.0.0.1" or item['ip'] == '': | |
164 | continue | |
165 | h_id = self.createAndAddHost( | |
166 | item['ip'], | |
167 | hostnames=item['hosts']) | |
168 | ||
169 | if item['isResolver']: | |
170 | s_id = self.createAndAddServiceToHost( | |
171 | h_id, | |
172 | "domain", | |
173 | "tcp", | |
174 | ports=['53']) | |
175 | ||
176 | if item['isZoneVuln']: | |
177 | self.createAndAddVulnToService( | |
178 | h_id, | |
179 | s_id, | |
180 | "Zone transfer", | |
181 | desc="A Dns server allows unrestricted zone transfers", | |
182 | ref=["CVE-1999-0532"]) | |
183 | ||
184 | def processCommandString(self, username, current_path, command_string): | |
185 | self._output_file_path = os.path.join( | |
186 | self.data_path, | |
187 | "%s_%s_output-%s.txt" % ( | |
188 | self.get_ws(), | |
189 | self.id, | |
190 | random.uniform(1, 10)) | |
191 | ) | |
192 | ||
193 | arg_match = self.xml_arg_re.match(command_string) | |
194 | ||
195 | if arg_match is None: | |
196 | return "%s > %s" % (command_string, self._output_file_path) | |
197 | else: | |
198 | return re.sub(arg_match.group(1), | |
199 | r"> %s" % self._output_file_path, | |
200 | command_string) | |
201 | ||
202 | ||
203 | def createPlugin(): | |
204 | return FiercePlugin() | |
205 | ||
206 | if __name__ == "__main__": | |
207 | import sys | |
208 | import os | |
209 | if len(sys.argv) == 2: | |
210 | report_file = sys.argv[1] | |
211 | if os.path.isfile(report_file): | |
212 | plugin = createPlugin() | |
213 | plugin.processReport(report_file) | |
214 | print(plugin.get_json()) | |
215 | else: | |
216 | print(f"Report not found: {report_file}") | |
217 | else: | |
218 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
219 | # I'm Py3 |
0 | import base64 | |
1 | import io | |
2 | import re | |
3 | from html.parser import HTMLParser | |
4 | from zipfile import ZipFile | |
5 | ||
6 | import html2text | |
7 | from lxml import objectify | |
8 | from faraday_plugins.plugins.plugin import PluginByExtension | |
9 | ||
10 | ||
11 | class FortifyPlugin(PluginByExtension): | |
12 | """ | |
13 | Example plugin to parse nmap output. | |
14 | """ | |
15 | ||
16 | def __init__(self): | |
17 | super().__init__() | |
18 | self.id = "Fortify" | |
19 | self.name = "Fortify XML Output Plugin" | |
20 | self.plugin_version = "0.0.1" | |
21 | self.extension = ".fpr" | |
22 | self.open_options = {"mode": "rb"} | |
23 | ||
24 | def _process_fvdl_vulns(self, fp): | |
25 | ||
26 | for host in fp.hosts.keys(): | |
27 | fp.hosts[host] = self.createAndAddHost(host) | |
28 | ||
29 | for vuln in fp.vulns.keys(): | |
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'], | |
36 | resolution="", | |
37 | data="", | |
38 | external_id=vuln.text | |
39 | ) | |
40 | ||
41 | def _process_webinspect_vulns(self, fp): | |
42 | for vuln_data in fp.sast_vulns: | |
43 | host_id = self.createAndAddHost( | |
44 | vuln_data['host'] or vuln_data['website']) | |
45 | ||
46 | service_name = vuln_data['service'].get('name', 'unknown') | |
47 | protocol_name = 'line number' | |
48 | if vuln_data['service']['port'] == '443': | |
49 | service_name = 'https' | |
50 | protocol_name = 'tcp' | |
51 | if vuln_data['service']['port'] == '80': | |
52 | service_name = 'http' | |
53 | protocol_name = 'tcp' | |
54 | ||
55 | service_id = self.createAndAddServiceToHost( | |
56 | host_id, | |
57 | service_name, | |
58 | protocol=protocol_name, | |
59 | ports=[vuln_data['service']['port']]) | |
60 | ||
61 | self.createAndAddVulnWebToService( | |
62 | host_id, service_id, | |
63 | vuln_data['name'], | |
64 | website=vuln_data['website'] or '', | |
65 | path=vuln_data['path'] or '', | |
66 | query=vuln_data['query'] or '', | |
67 | method=vuln_data['method'] or '', | |
68 | request=vuln_data['request'] or '', | |
69 | ref=vuln_data['references'], | |
70 | response=vuln_data['response'] or '', | |
71 | desc=vuln_data['description'], | |
72 | #resolution=vuln_data[''], | |
73 | severity=vuln_data['severity'] | |
74 | ) | |
75 | ||
76 | def parseOutputString(self, output, debug=False): | |
77 | fp = FortifyParser(output) | |
78 | if fp.fvdl is not None: | |
79 | self._process_fvdl_vulns(fp) | |
80 | if fp.webinspect is not None: | |
81 | self._process_webinspect_vulns(fp) | |
82 | ||
83 | return True | |
84 | ||
85 | ||
86 | class FortifyParser: | |
87 | """ | |
88 | Parser for fortify on demand | |
89 | """ | |
90 | ||
91 | def __init__(self, output): | |
92 | self.vulns = {} | |
93 | self.sast_vulns = [] | |
94 | self.hosts = {} | |
95 | self.fvdl = None | |
96 | self.webinspect = None | |
97 | self.audit = None | |
98 | self.suppressed = [] | |
99 | self.vuln_classes = [] | |
100 | self.descriptions = {} | |
101 | ||
102 | self._uncompress_fpr(output) | |
103 | self._extract_vulns() | |
104 | self._prepare_description_templates() | |
105 | ||
106 | # regexes used in format_description | |
107 | self.remove_extra_chars = re.compile(r'&(\w*);') | |
108 | self.replacements_idx = re.compile(r'<Replace key="(.*?)"[\s\/].*?>') | |
109 | self.replacements_holders = re.compile(r'<Replace key=".*?"[\s\/].*?>') | |
110 | self.replacements_idx2 = re.compile(r'<Replace key="(.*?)"(\slink="(.*?)")?[\s\/].*?>') | |
111 | ||
112 | def _uncompress_fpr(self, output): | |
113 | with ZipFile(io.BytesIO(output)) as fprcontent: | |
114 | try: | |
115 | self.fvdl = objectify.fromstring(fprcontent.read('audit.fvdl')) | |
116 | except KeyError: | |
117 | pass | |
118 | try: | |
119 | self.webinspect = objectify.fromstring(fprcontent.read('webinspect.xml')) | |
120 | except KeyError: | |
121 | pass | |
122 | try: | |
123 | self.audit = objectify.fromstring(fprcontent.read('audit.xml')) | |
124 | except KeyError: | |
125 | pass | |
126 | ||
127 | def _process_fvdl(self): | |
128 | for vuln in self.fvdl.Vulnerabilities.iterchildren(): | |
129 | ||
130 | vulnID = vuln.InstanceInfo.InstanceID | |
131 | ||
132 | if vulnID in self.suppressed: | |
133 | continue | |
134 | ||
135 | self.vulns[vulnID] = {} | |
136 | ||
137 | # the last children of Primary (Entry tags) always contains vuln filename ,path and line | |
138 | _last_entry = None | |
139 | for _last_entry in vuln.AnalysisInfo.Unified.Trace.Primary.iterchildren(): | |
140 | pass | |
141 | ||
142 | path = _last_entry.Node.SourceLocation.get('path') | |
143 | ||
144 | self.vulns[vulnID]['host'] = path | |
145 | self.vulns[vulnID]['name'] = "{} {}".format(vuln.ClassInfo.Type, | |
146 | getattr(vuln.ClassInfo, "Subtype", "")) | |
147 | self.vulns[vulnID]['class'] = vuln.ClassInfo.ClassID | |
148 | self.vulns[vulnID]['replacements'] = {} | |
149 | ||
150 | self.vulns[vulnID]['severity'] = self.calculate_severity(vuln) | |
151 | ||
152 | # placeholder for storing hosts ids when created in main plugin method | |
153 | if path not in self.hosts.keys(): | |
154 | self.hosts[path] = None | |
155 | ||
156 | if vuln.ClassInfo.ClassID not in self.vuln_classes: | |
157 | self.vuln_classes.append(vuln.ClassInfo.ClassID) | |
158 | ||
159 | # fortify bug that when it has no replacements, shows blank in fortify dashboard | |
160 | if not hasattr(vuln.AnalysisInfo.Unified, "ReplacementDefinitions"): | |
161 | self.vulns[vulnID]['replacements'] = None | |
162 | continue | |
163 | ||
164 | try: | |
165 | getattr(vuln.AnalysisInfo.Unified, "ReplacementDefinitions") | |
166 | ||
167 | for repl in vuln.AnalysisInfo.Unified.ReplacementDefinitions.iterchildren( | |
168 | tag="{xmlns://www.fortifysoftware.com/schema/fvdl}Def"): | |
169 | ||
170 | repl_val = repl.get('key') | |
171 | if repl.get('link'): | |
172 | repl_val = repl.get('link') | |
173 | ||
174 | self.vulns[vulnID]['replacements'][repl_val] = repl.get('value') | |
175 | except AttributeError: | |
176 | self.vulns[vulnID]['replacements'] = None | |
177 | ||
178 | def _process_webinspect(self): | |
179 | for session in self.webinspect.getchildren(): | |
180 | hostname = session.Host.text | |
181 | port = session.Port.text | |
182 | service_data = {} | |
183 | if port: | |
184 | service_data['port'] = port | |
185 | ||
186 | path = session.Request.Path.text | |
187 | query = session.Request.FullQuery.text | |
188 | method = session.Request.Method.text | |
189 | request = '' | |
190 | if session.RawRequest.text: | |
191 | request = base64.b64decode(session.RawRequest.text) | |
192 | response = '' | |
193 | if session.RawResponse.text: | |
194 | response = base64.b64decode(session.RawResponse.text) | |
195 | status_code = session.Response.StatusCode.text | |
196 | ||
197 | for issues in session.Issues: | |
198 | for issue_data in issues.getchildren(): | |
199 | params = '' | |
200 | check_type = issue_data.CheckTypeID | |
201 | if check_type.text.lower() != 'vulnerability': | |
202 | # TODO: when plugins accept tags, we shoudl this as a tag. | |
203 | pass | |
204 | name = issue_data.Name.text | |
205 | external_id = issue_data.VulnerabilityID.text | |
206 | faraday_severities = { | |
207 | 0: 'info', | |
208 | 1: 'low', | |
209 | 2: 'med', | |
210 | 3: 'high', | |
211 | 4: 'critical' | |
212 | } | |
213 | severity = faraday_severities[issue_data.Severity] | |
214 | references = [] | |
215 | try: | |
216 | classifications = issue_data.Classifications.getchildren() | |
217 | except AttributeError: | |
218 | classifications = [] | |
219 | ||
220 | for classification in classifications: | |
221 | references.append(classification.text) | |
222 | ||
223 | # Build description | |
224 | description = u'' | |
225 | for report_section in issue_data.findall('./ReportSection'): | |
226 | description += u'{} \n'.format(report_section.Name.text) | |
227 | description += u'{} \n'.format(report_section.SectionText.text) | |
228 | description += u'{} \n'.format(issue_data.get('id')) | |
229 | ||
230 | h = html2text.HTML2Text() | |
231 | description = h.handle(description) | |
232 | ||
233 | for repro_step in issue_data.findall('./ReproSteps'): | |
234 | step = repro_step.ReproStep | |
235 | if step is not None: | |
236 | try: | |
237 | params = step.PostParams.text | |
238 | except AttributeError: | |
239 | pass | |
240 | ||
241 | if not hostname: | |
242 | # This seems to be a mobile app | |
243 | hostname = session.URL.text | |
244 | ||
245 | if not port: | |
246 | service_data['name'] = step.Url.text | |
247 | service_data['port'] = step.sourceline | |
248 | ||
249 | self.sast_vulns.append({ | |
250 | "host": hostname, | |
251 | "severity": severity, | |
252 | "service": service_data, | |
253 | "name": name, | |
254 | "description": description, | |
255 | "external_id": external_id, | |
256 | "references": references, | |
257 | "method": method, | |
258 | "query": query, | |
259 | "response": response, | |
260 | "request": request, | |
261 | "path": path, | |
262 | "params": params, | |
263 | "status_code": status_code, | |
264 | "website": session.URL.text | |
265 | }) | |
266 | ||
267 | def _extract_vulns(self): | |
268 | # make list of false positives | |
269 | try: | |
270 | issue_list = self.audit.IssueList.iterchildren() | |
271 | except AttributeError: | |
272 | issue_list = [] | |
273 | ||
274 | for issue in issue_list: | |
275 | if issue.get('suppressed', 'false').lower() == 'true': | |
276 | self.suppressed.append(issue.get('instanceId')) | |
277 | ||
278 | if self.fvdl is not None: | |
279 | self._process_fvdl() | |
280 | ||
281 | if self.webinspect is not None: | |
282 | self._process_webinspect() | |
283 | ||
284 | def calculate_severity(self, vuln): | |
285 | ||
286 | severity = None # ["critical", "high", "medium", "low", "informational", "unclassified"] | |
287 | rulepath = objectify.ObjectPath("FVDL.EngineData.RuleInfo.Rule") | |
288 | impact = None | |
289 | probability = None | |
290 | accuracy = None | |
291 | ||
292 | # XML path /FVDL/EngineData/RuleInfo/Rule (many)/MetaInfo/Group (many) the attribute "name" | |
293 | # are keys for vuln properties | |
294 | ||
295 | for rule in rulepath(self.fvdl): | |
296 | if rule.get('id') == vuln.ClassInfo.ClassID: | |
297 | for group in rule.MetaInfo.iterchildren(): | |
298 | if group.get('name') == "Probability": | |
299 | probability = group | |
300 | if group.get('name') == "Impact": | |
301 | impact = group | |
302 | if group.get('name') == "Accuracy": | |
303 | accuracy = group | |
304 | ||
305 | likelihood = (accuracy * vuln.InstanceInfo.Confidence * probability) / 25 | |
306 | ||
307 | if impact and probability: | |
308 | ||
309 | if impact >= 2.5 and likelihood >= 2.5: | |
310 | severity = 'critical' | |
311 | elif impact >= 2.5 > likelihood: | |
312 | severity = 'high' | |
313 | elif impact < 2.5 <= likelihood: | |
314 | severity = 'medium' | |
315 | elif impact < 2.5 and likelihood < 2.5: | |
316 | severity = 'low' | |
317 | else: | |
318 | print("missing severity") | |
319 | ||
320 | # print("{}:{}:{}".format(vuln.InstanceInfo.InstanceID, vuln.InstanceInfo.InstanceSeverity, severity)) | |
321 | return severity | |
322 | ||
323 | def concat_vuln_name(self, vuln): | |
324 | return "{} {} {}:{}".format(vuln.ClassInfo.Type, vuln.ClassInfo.Subtype, | |
325 | self.vulns[vuln.InstanceInfo.InstanceID]['filename'], | |
326 | self.vulns[vuln.InstanceInfo.InstanceID]['line']) | |
327 | ||
328 | def _prepare_description_templates(self): | |
329 | if self.fvdl is None: | |
330 | return | |
331 | for description in self.fvdl.Description: | |
332 | ||
333 | self.descriptions[description.get("classID")] = {} | |
334 | ||
335 | if description.get('classID') not in self.vuln_classes: | |
336 | continue | |
337 | ||
338 | tips = "" | |
339 | if hasattr(description, 'Tips'): | |
340 | for tip in description.Tips.getchildren(): | |
341 | tips += "\n" + tip.text | |
342 | ||
343 | htmlparser = HTMLParser() | |
344 | self.descriptions[description.get("classID")]['text'] = htmlparser.unescape( | |
345 | "Summary:\n{}\n\nExplanation:\n{}\n\nRecommendations:\n{}\n\nTips:{}".format( | |
346 | description.Abstract, description.Explanation, description.Recommendations, tips)) | |
347 | ||
348 | # group vuln references | |
349 | references = [] | |
350 | try: | |
351 | children = description.References.getchildren() | |
352 | except AttributeError: | |
353 | children = [] | |
354 | ||
355 | for reference in children: | |
356 | ||
357 | for attr in dir(reference): | |
358 | if attr == '__class__': | |
359 | break | |
360 | ||
361 | references.append("{}: {}\n".format(attr, getattr(reference, attr))) | |
362 | ||
363 | self.descriptions[description.get("classID")]['references'] = references | |
364 | ||
365 | def format_description(self, vulnID): | |
366 | ||
367 | text = self.descriptions[self.vulns[vulnID]['class']]['text'] | |
368 | replacements = self.vulns[vulnID]['replacements'] | |
369 | if not replacements: | |
370 | return text | |
371 | ||
372 | # special chars that must shown as-is, have the hmtlentity value duplicated | |
373 | text = self.remove_extra_chars.sub(r"&\1;", text) | |
374 | ||
375 | for placeholder in self.replacements_holders.findall(text, re.MULTILINE): | |
376 | ||
377 | torepl = '<Replace key="{}"/>' | |
378 | match = self.replacements_idx2.search(placeholder) | |
379 | ||
380 | replace_with = "" | |
381 | if match: | |
382 | idx = match.group(1) | |
383 | if match.group(3): | |
384 | idx = match.group(3) | |
385 | _filekey = "{}.file".format(idx) | |
386 | _linekey = "{}.line".format(idx) | |
387 | text = text.replace(placeholder, "").replace( | |
388 | torepl.format(_filekey), replacements[_filekey]).replace( | |
389 | torepl.format(_linekey), replacements[_linekey]) | |
390 | continue | |
391 | ||
392 | try: | |
393 | replace_with = replacements[idx] | |
394 | except KeyError: | |
395 | # Nothing to replace, use empty string | |
396 | text = text.replace(placeholder, "") | |
397 | ||
398 | text = text.replace(placeholder, replace_with) | |
399 | ||
400 | text += '{}\n Instance ID: {} \n'.format(text, vulnID) | |
401 | h = html2text.HTML2Text() | |
402 | return text | |
403 | ||
404 | ||
405 | def createPlugin(): | |
406 | return FortifyPlugin() | |
407 | ||
408 | ||
409 | if __name__ == "__main__": | |
410 | import sys | |
411 | import os | |
412 | if len(sys.argv) == 2: | |
413 | report_file = sys.argv[1] | |
414 | if os.path.isfile(report_file): | |
415 | plugin = createPlugin() | |
416 | plugin.processReport(report_file) | |
417 | print(plugin.get_json()) | |
418 | else: | |
419 | print(f"Report not found: {report_file}") | |
420 | else: | |
421 | print(f"USAGE {sys.argv[0]} REPORT_FILE") |
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 | #!/usr/bin/python | |
1 | """ | |
2 | Copyright (C) 2016 xtr4nge [_AT_] gmail.com | |
3 | ||
4 | This program is free software: you can redistribute it and/or modify | |
5 | it under the terms of the GNU General Public License as published by | |
6 | the Free Software Foundation, either version 3 of the License, or | |
7 | (at your option) any later version. | |
8 | ||
9 | This program is distributed in the hope that it will be useful, | |
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | GNU General Public License for more details. | |
13 | ||
14 | You should have received a copy of the GNU General Public License | |
15 | along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | """ | |
17 | ||
18 | import sys | |
19 | import getopt | |
20 | import json | |
21 | import requests | |
22 | ||
23 | requests.packages.urllib3.disable_warnings() # DISABLE SSL CHECK WARNINGS | |
24 | ||
25 | gVersion = "1.0" | |
26 | server = "http://127.0.0.1:8000" | |
27 | token = "e5dab9a69988dd65e578041416773149ea57a054" | |
28 | ||
29 | ||
30 | def usage(): | |
31 | print("\nFruityWiFi API " + gVersion + " by @xtr4nge") | |
32 | ||
33 | print("Usage: ./client <options>\n") | |
34 | print("Options:") | |
35 | print("-x <command>, --execute=<commnd> exec the command passed as parameter.") | |
36 | print("-t <token>, --token=<token> authentication token.") | |
37 | print("-s <server>, --server=<server> FruityWiFi server [http{s}://ip:port].") | |
38 | print("-h Print this help message.") | |
39 | print("") | |
40 | print("FruityWiFi: http://www.fruitywifi.com") | |
41 | print("") | |
42 | ||
43 | ||
44 | def parseOptions(argv): | |
45 | ||
46 | v_execute = "/log/dhcp" | |
47 | v_token = token | |
48 | v_server = server | |
49 | ||
50 | try: | |
51 | opts, args = getopt.getopt(argv, "hx:t:s:", | |
52 | ["help","execute=","token=","server="]) | |
53 | ||
54 | for opt, arg in opts: | |
55 | if opt in ("-h", "--help"): | |
56 | usage() | |
57 | sys.exit() | |
58 | elif opt in ("-x", "--execute"): | |
59 | v_execute = arg | |
60 | elif opt in ("-t", "--token"): | |
61 | v_token = arg | |
62 | elif opt in ("-s", "--server"): | |
63 | v_server = arg | |
64 | ||
65 | return (v_execute, v_token, v_server) | |
66 | ||
67 | except getopt.GetoptError: | |
68 | usage() | |
69 | sys.exit(2) | |
70 | ||
71 | ||
72 | (execute, token, server) = parseOptions(sys.argv[1:]) | |
73 | ||
74 | ||
75 | class Webclient: | |
76 | ||
77 | def __init__(self, server, token): | |
78 | ||
79 | self.global_webserver = server | |
80 | self.path = "/modules/api/includes/ws_action.php" | |
81 | self.s = requests.session() | |
82 | self.token = token | |
83 | ||
84 | def login(self): | |
85 | ||
86 | payload = { | |
87 | 'action': 'login', | |
88 | 'token': self.token | |
89 | } | |
90 | ||
91 | self.s = requests.session() | |
92 | self.s.get(self.global_webserver, verify=False) # DISABLE SSL CHECK | |
93 | self.s.post(self.global_webserver + '/login.php', data=payload) | |
94 | ||
95 | def loginCheck(self): | |
96 | ||
97 | response = self.s.get(self.global_webserver + '/login_check.php') | |
98 | ||
99 | if response.text != "": | |
100 | self.login() | |
101 | ||
102 | if response.text != "": | |
103 | print(json.dumps("[FruityWiFi]: Ah, Ah, Ah! You didn't say the magic word! (check API token and server)")) | |
104 | sys.exit() | |
105 | ||
106 | return True | |
107 | ||
108 | def submitPost(self, data): | |
109 | response = self.s.post(self.global_webserver + data) | |
110 | return response.json | |
111 | ||
112 | def submitGet(self, data): | |
113 | response = self.s.get(self.global_webserver + self.path + "?" + data) | |
114 | ||
115 | return response | |
116 | ||
117 | try: | |
118 | w = Webclient(server, token) | |
119 | w.login() | |
120 | w.loginCheck() | |
121 | except Exception as e: | |
122 | print(json.dumps("[FruityWiFi]: There is something wrong (%s)" % e)) | |
123 | sys.exit(1) | |
124 | ||
125 | _exec = "/log/dhcp" | |
126 | _exec = execute | |
127 | if _exec != "": | |
128 | try: | |
129 | out = w.submitGet("api=" + str(_exec)) | |
130 | json_output = out.json() | |
131 | except Exception as e: | |
132 | print(json.dumps("[FruityWiFi]: There is something wrong (%s)" % e)) | |
133 | sys.exit(1) | |
134 | ||
135 | output = [] | |
136 | if _exec == "/log/dhcp": | |
137 | for item in json_output: | |
138 | if item.strip() != "": | |
139 | output = [item.split(" ")] | |
140 | else: | |
141 | output = json_output | |
142 | ||
143 | if len(output) > 0: | |
144 | print(json.dumps(output)) | |
145 | else: | |
146 | print(json.dumps("No clients connected")) | |
147 | ||
148 | ||
149 | # 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 json | |
9 | import traceback | |
10 | ||
11 | __author__ = "xtr4nge" | |
12 | __copyright__ = "Copyright (c) 2016, FruityWiFi" | |
13 | __credits__ = ["xtr4nge"] | |
14 | __license__ = "" | |
15 | __version__ = "1.0.0" | |
16 | __maintainer__ = "xtr4nge" | |
17 | __email__ = "@xtr4nge" | |
18 | __status__ = "Development" | |
19 | ||
20 | class FruityWiFiPlugin(PluginBase): | |
21 | """ | |
22 | This plugin handles FruityWiFi clients. | |
23 | """ | |
24 | ||
25 | def __init__(self): | |
26 | super().__init__() | |
27 | self.id = "fruitywifi" | |
28 | self.name = "FruityWiFi" | |
29 | self.plugin_version = "0.0.1" | |
30 | self.version = "2.4" | |
31 | self.description = "http://www.fruitywifi.com" | |
32 | self.options = None | |
33 | self._current_output = None | |
34 | self.target = None | |
35 | ||
36 | self._command_regex = re.compile( | |
37 | r'^(fruitywifi).*?') | |
38 | ||
39 | self.addSetting("Token", str, "e5dab9a69988dd65e578041416773149ea57a054") | |
40 | self.addSetting("Server", str, "http://127.0.0.1:8000") | |
41 | self.addSetting("Severity", str, "high") | |
42 | ||
43 | def getSeverity(self, severity): | |
44 | if severity.lower() == "critical" or severity == "4": | |
45 | return 4 | |
46 | elif severity.lower() == "high" or severity == "3": | |
47 | return 3 | |
48 | elif severity.lower() == "med" or severity == "2": | |
49 | return 2 | |
50 | elif severity.lower() == "low" or severity == "1": | |
51 | return 1 | |
52 | elif severity.lower() == "info" or severity == "0": | |
53 | return 0 | |
54 | else: | |
55 | return 5 | |
56 | ||
57 | def createHostInterfaceVuln(self, ip_address, macaddress, hostname, desc, vuln_name, severity): | |
58 | h_id = self.createAndAddHost(ip_address) | |
59 | if self._isIPV4(ip_address): | |
60 | i_id = self.createAndAddInterface( | |
61 | h_id, | |
62 | ip_address, | |
63 | macaddress, | |
64 | ipv4_address=ip_address, | |
65 | hostname_resolution=[hostname] | |
66 | ) | |
67 | else: | |
68 | self.createAndAddInterface( | |
69 | h_id, ip_address, ipv6_address=ip_address, hostname_resolution=[hostname]) | |
70 | ||
71 | self.createAndAddVulnToHost( | |
72 | h_id, | |
73 | vuln_name, | |
74 | desc=desc, | |
75 | ref=["http://www.fruitywifi.com/"], | |
76 | severity=severity | |
77 | ) | |
78 | ||
79 | def parseOutputString(self, output, debug=False): | |
80 | ||
81 | try: | |
82 | output = json.loads(output) | |
83 | ||
84 | if len(output) > 0: | |
85 | ||
86 | if len(output[0]) == 3: | |
87 | ||
88 | severity = self.getSeverity(self.getSetting("Severity")) | |
89 | ||
90 | for item in output: | |
91 | ip_address = item[0] | |
92 | macaddress = item[1] | |
93 | hostname = item[2] | |
94 | vuln_name = "FruityWiFi" | |
95 | severity = severity | |
96 | ||
97 | desc = "Client ip: " + ip_address + \ | |
98 | " has been connected to FruityWiFi\n" | |
99 | desc += "More information:" | |
100 | desc += "\nname: " + hostname | |
101 | ||
102 | self.createHostInterfaceVuln(ip_address, macaddress, hostname, desc, vuln_name, severity) | |
103 | ||
104 | elif len(output[0]) == 5: | |
105 | for item in output: | |
106 | ip_address = item[0] | |
107 | macaddress = item[1] | |
108 | hostname = item[2] | |
109 | vuln_name = item[3] | |
110 | severity = item[4] | |
111 | ||
112 | desc = "Client ip: " + ip_address + \ | |
113 | " has been connected to FruityWiFi\n" | |
114 | desc += "More information:" | |
115 | desc += "\nname: " + hostname | |
116 | ||
117 | self.createHostInterfaceVuln(ip_address, macaddress, hostname, desc, vuln_name, severity) | |
118 | ||
119 | except: | |
120 | traceback.print_exc() | |
121 | ||
122 | return True | |
123 | ||
124 | def _isIPV4(self, ip): | |
125 | if len(ip.split(".")) == 4: | |
126 | return True | |
127 | else: | |
128 | return False | |
129 | ||
130 | def processCommandString(self, username, current_path, command_string, debug=False): | |
131 | """ | |
132 | """ | |
133 | #params = command_string.replace("fruitywifi","") | |
134 | params = "-t %s -s %s" % (self.getSetting("Token"), self.getSetting("Server")) | |
135 | ||
136 | return "python " + os.path.dirname(__file__) + "/fruitywifi.py " + params | |
137 | #return None | |
138 | ||
139 | def createPlugin(): | |
140 | return FruityWiFiPlugin() | |
141 | ||
142 | if __name__ == "__main__": | |
143 | import sys | |
144 | import os | |
145 | if len(sys.argv) == 2: | |
146 | report_file = sys.argv[1] | |
147 | if os.path.isfile(report_file): | |
148 | plugin = createPlugin() | |
149 | plugin.processReport(report_file) | |
150 | print(plugin.get_json()) | |
151 | else: | |
152 | print(f"Report not found: {report_file}") | |
153 | else: | |
154 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
155 | ||
156 | # 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 | # 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 | current_path = os.path.abspath(os.getcwd()) | |
12 | ||
13 | __author__ = "Javier Victor Mariano Bruno" | |
14 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
15 | __credits__ = ["Javier Victor Mariano Bruno"] | |
16 | __license__ = "" | |
17 | __version__ = "1.0.0" | |
18 | __maintainer__ = "Javier Victor Mariano Bruno" | |
19 | __email__ = "[email protected]" | |
20 | __status__ = "Development" | |
21 | ||
22 | ||
23 | class CmdFtpPlugin(PluginBase): | |
24 | """ | |
25 | This plugin handles ftp command. | |
26 | Basically detects if user was able to connect to a device | |
27 | """ | |
28 | ||
29 | def __init__(self): | |
30 | super().__init__() | |
31 | self.id = "ftp" | |
32 | self.name = "Ftp" | |
33 | self.plugin_version = "0.0.1" | |
34 | self.version = "0.17" | |
35 | self.framework_version = "1.0.0" | |
36 | self.options = None | |
37 | self._current_output = None | |
38 | self._command_regex = re.compile(r'^ftp.*?') | |
39 | self._host_ip = None | |
40 | self._port = "21" | |
41 | self._info = 0 | |
42 | self._version = None | |
43 | ||
44 | global current_path | |
45 | ||
46 | def resolve(self, host): | |
47 | try: | |
48 | return socket.gethostbyname(host) | |
49 | except: | |
50 | pass | |
51 | return host | |
52 | ||
53 | def parseOutputString(self, output, debug=False): | |
54 | ||
55 | host_info = re.search(r"Connected to (.+)\.", output) | |
56 | banner = re.search("220?([\w\W]+)$", output) | |
57 | if re.search("Connection timed out", output) is None and host_info is not None: | |
58 | hostname = host_info.group(1) | |
59 | ip_address = self.resolve(hostname) | |
60 | self._version = banner.groups(0) if banner else "" | |
61 | if debug: | |
62 | print(ip_address) | |
63 | ||
64 | h_id = self.createAndAddHost(ip_address) | |
65 | ||
66 | i_id = self.createAndAddInterface( | |
67 | h_id, | |
68 | ip_address, | |
69 | ipv4_address=ip_address, | |
70 | hostname_resolution=[hostname]) | |
71 | ||
72 | s_id = self.createAndAddServiceToInterface( | |
73 | h_id, | |
74 | i_id, | |
75 | "ftp", | |
76 | "tcp", | |
77 | ports=[self._port], | |
78 | status="open") | |
79 | ||
80 | if debug is True: | |
81 | self.logger.info("Debug is active") | |
82 | ||
83 | return True | |
84 | ||
85 | def processCommandString(self, username, current_path, command_string): | |
86 | """ | |
87 | """ | |
88 | count_args = command_string.split() | |
89 | ||
90 | c = count_args.__len__() | |
91 | self._port = "21" | |
92 | if re.search("[\d]+", count_args[c - 1]): | |
93 | self._port = count_args[c - 1] | |
94 | ||
95 | ||
96 | def createPlugin(): | |
97 | return CmdFtpPlugin() | |
98 | ||
99 | if __name__ == "__main__": | |
100 | import sys | |
101 | import os | |
102 | if len(sys.argv) == 2: | |
103 | report_file = sys.argv[1] | |
104 | if os.path.isfile(report_file): | |
105 | plugin = createPlugin() | |
106 | plugin.processReport(report_file) | |
107 | print(plugin.get_json()) | |
108 | else: | |
109 | print(f"Report not found: {report_file}") | |
110 | else: | |
111 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
112 | # 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 | # 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 | from faraday_plugins.plugins.plugin import PluginBase | |
6 | import socket | |
7 | import re | |
8 | import os | |
9 | ||
10 | current_path = os.path.abspath(os.getcwd()) | |
11 | ||
12 | __author__ = "Francisco Amato" | |
13 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
14 | __credits__ = ["Francisco Amato"] | |
15 | __license__ = "" | |
16 | __version__ = "1.0.0" | |
17 | __maintainer__ = "Francisco Amato" | |
18 | __email__ = "[email protected]" | |
19 | __status__ = "Development" | |
20 | ||
21 | ||
22 | class GoohostParser: | |
23 | """ | |
24 | The objective of this class is to parse an xml file generated by the goohost tool. | |
25 | ||
26 | TODO: Handle errors. | |
27 | TODO: Test goohost output version. Handle what happens if the parser doesn't support it. | |
28 | TODO: Test cases. | |
29 | ||
30 | @param goohost_scantype You could select scan type ip, mail or host | |
31 | """ | |
32 | ||
33 | def __init__(self, output, goohost_scantype): | |
34 | ||
35 | self.items = [] | |
36 | lines = list(filter(None, output.split('\n'))) | |
37 | for line in lines: | |
38 | if goohost_scantype == 'ip': | |
39 | data = line.split() | |
40 | item = {'host': data[0], 'ip': data[1]} | |
41 | self.add_host_info_to_items(item['ip'], item['host']) | |
42 | elif goohost_scantype == 'host': | |
43 | data = line.strip() | |
44 | item = {'host': data, 'ip': self.resolve(data)} | |
45 | self.add_host_info_to_items(item['ip'], item['host']) | |
46 | else: | |
47 | item = {'data': line} | |
48 | ||
49 | def resolve(self, host): | |
50 | try: | |
51 | return socket.gethostbyname(host) | |
52 | except: | |
53 | pass | |
54 | return host | |
55 | ||
56 | def add_host_info_to_items(self, ip_address, hostname): | |
57 | data = {} | |
58 | exists = False | |
59 | for item in self.items: | |
60 | if ip_address in item['ip']: | |
61 | item['hosts'].append(hostname) | |
62 | exists = True | |
63 | ||
64 | if not exists: | |
65 | data['ip'] = ip_address | |
66 | data['hosts'] = [hostname] | |
67 | self.items.append(data) | |
68 | ||
69 | ||
70 | class GoohostPlugin(PluginBase): | |
71 | """ | |
72 | Example plugin to parse goohost output. | |
73 | """ | |
74 | ||
75 | def __init__(self): | |
76 | super().__init__() | |
77 | self.id = "Goohost" | |
78 | self.name = "Goohost XML Output Plugin" | |
79 | self.plugin_version = "0.0.1" | |
80 | self.version = "v.0.0.1" | |
81 | 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).*?') | |
86 | self.host = None | |
87 | ||
88 | global current_path | |
89 | self.output_path = None | |
90 | self._command_string = None | |
91 | ||
92 | def parseOutputString(self, output, debug=False): | |
93 | """ | |
94 | This method will check if the import was made through the console or by importing a Goohost report. | |
95 | ||
96 | Import from Console:The method will take the path of the report generated by Goohost from the output the shell sends and will read | |
97 | the information from the txt where it expects it to be present. | |
98 | ||
99 | Import from Report: The method receives the output of the txt report as parameter. | |
100 | ||
101 | self.scantype defines the method used to generate the Goohost report | |
102 | ||
103 | NOTE: if 'debug' is true then it is being run from a test case and the | |
104 | output being sent is valid. | |
105 | """ | |
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 | ||
126 | 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 | ||
135 | def define_scantype_by_command(self, command): | |
136 | method_regex = re.compile(r'-m (mail|host|ip)') | |
137 | method = method_regex.search(command) | |
138 | if method: | |
139 | return method.group(1) | |
140 | ||
141 | return 'host' | |
142 | ||
143 | def define_scantype_by_output(self, output): | |
144 | lines = output.split('\n') | |
145 | line = lines[0].split(' ') | |
146 | ||
147 | if len(line) == 1: | |
148 | return 'host' | |
149 | elif len(line) == 2: | |
150 | return 'ip' | |
151 | ||
152 | def read_output_file(self, report_path): | |
153 | mypath = re.search("Results saved in file (\S+)", report_path) | |
154 | if not mypath: | |
155 | return False | |
156 | else: | |
157 | self.output_path = self._current_path + "/" + mypath.group(1) | |
158 | if not os.path.exists(self.output_path): | |
159 | return False | |
160 | with open(self.output_path, 'r') as report: | |
161 | output = report.read() | |
162 | ||
163 | return output | |
164 | ||
165 | ||
166 | def createPlugin(): | |
167 | return GoohostPlugin() | |
168 | ||
169 | if __name__ == "__main__": | |
170 | import sys | |
171 | import os | |
172 | if len(sys.argv) == 2: | |
173 | report_file = sys.argv[1] | |
174 | if os.path.isfile(report_file): | |
175 | plugin = createPlugin() | |
176 | plugin.processReport(report_file) | |
177 | print(plugin.get_json()) | |
178 | else: | |
179 | print(f"Report not found: {report_file}") | |
180 | else: | |
181 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
182 | # 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 | # I'm Py3⏎ |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2017 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | """ | |
5 | from faraday_plugins.plugins.plugin import PluginBase | |
6 | import re | |
7 | ||
8 | __author__ = "Roberto Focke" | |
9 | __copyright__ = "Copyright (c) 2017, Infobyte LLC" | |
10 | __license__ = "" | |
11 | __version__ = "1.0.0" | |
12 | ||
13 | ||
14 | class hping3(PluginBase): | |
15 | ||
16 | def __init__(self): | |
17 | super().__init__() | |
18 | self.id = "Hping3" | |
19 | self.name = "hping3" | |
20 | self.plugin_version = "0.0.1" | |
21 | self.version = "1.0.0" | |
22 | self.srv = {'21': ' ftp', '80': 'http', '143': 'imap', '1433': 'mssql', | |
23 | '3306': 'mysql', '524': 'ncp', '119': 'nntp', | |
24 | '5631': 'pcanywhere', '110': 'pop3', '5432': 'postgres', | |
25 | '512': 'rexec', '513': 'rlogin', '514': 'rsh', | |
26 | '25': 'smtp', '161': 'snmp', '22': 'ssh', '3690': 'svn', | |
27 | '23': 'telnet', '5900': 'vnc'} | |
28 | ||
29 | self._command_regex = re.compile(r'^(sudo hping3|hping3)\s+.*$') | |
30 | ||
31 | def parseOutputString(self, output, debug=False): | |
32 | ||
33 | regex_ipv4 = re.search(r"(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\)\:", output) | |
34 | if regex_ipv4: | |
35 | ip_address = regex_ipv4.group(0).rstrip("):") # Regex pls | |
36 | else: | |
37 | # Exit plugin, ip address not found. bad output | |
38 | self.log("Abort plugin: Ip address not found", "INFO") | |
39 | return | |
40 | ||
41 | hostname = output.split(" ")[1] | |
42 | host_id = self.createAndAddHost(hostname) | |
43 | ||
44 | i_id = self.createAndAddInterface( | |
45 | host_id, ip_address, ipv4_address=ip_address, hostname_resolution=[hostname]) | |
46 | ||
47 | if re.match("HPING", output): | |
48 | ||
49 | sport = re.search(r"sport=(\d{1,6})", output) | |
50 | ssport = [sport.group(1)] | |
51 | reci = re.search(r"flags=(\w{2,3})", output) | |
52 | service = self.srv[sport.group(1)] | |
53 | ||
54 | if reci.group(1) == "SA": | |
55 | s_id = self.createAndAddServiceToInterface( | |
56 | host_id, i_id, service, protocol="tcp", ports=ssport, status="open") | |
57 | ||
58 | lineas = output.split("\n") | |
59 | ||
60 | for linea in lineas: | |
61 | if (re.match(" ", linea)): | |
62 | ||
63 | list = re.findall("\w+", linea) | |
64 | service = list[1] | |
65 | port = [list[0]] | |
66 | ||
67 | if list[2] == "S" and list[3] == "A": | |
68 | s_id = self.createAndAddServiceToInterface( | |
69 | host_id, i_id, service, protocol="tcp", ports=port, status="open") | |
70 | ||
71 | def processCommandString(self, username, current_path, command_string): | |
72 | return None | |
73 | ||
74 | ||
75 | def createPlugin(): | |
76 | return hping3() | |
77 | ||
78 | if __name__ == "__main__": | |
79 | import sys | |
80 | import os | |
81 | if len(sys.argv) == 2: | |
82 | report_file = sys.argv[1] | |
83 | if os.path.isfile(report_file): | |
84 | plugin = createPlugin() | |
85 | plugin.processReport(report_file) | |
86 | print(plugin.get_json()) | |
87 | else: | |
88 | print(f"Report not found: {report_file}") | |
89 | else: | |
90 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
91 | # 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 | # 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 | from faraday_plugins.plugins.plugin import PluginBase | |
6 | import re | |
7 | import os | |
8 | import random | |
9 | ||
10 | current_path = os.path.abspath(os.getcwd()) | |
11 | ||
12 | __author__ = "Francisco Amato" | |
13 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
14 | __credits__ = ["Francisco Amato"] | |
15 | __license__ = "" | |
16 | __version__ = "1.0.0" | |
17 | __maintainer__ = "Francisco Amato" | |
18 | __email__ = "[email protected]" | |
19 | __status__ = "Development" | |
20 | ||
21 | ||
22 | class HydraParser: | |
23 | """ | |
24 | The objective of this class is to parse an xml file generated by the hydra tool. | |
25 | ||
26 | @param hydra_filepath A proper simple report generated by hydra | |
27 | """ | |
28 | ||
29 | def __init__(self, xml_output): | |
30 | lines = xml_output.splitlines() | |
31 | self.items = [] | |
32 | for l in lines: | |
33 | ||
34 | reg = re.search( | |
35 | "\[([^$]+)\]\[([^$]+)\] host: ([^$]+) login: ([^$]+) password: ([^$]+)", | |
36 | l) | |
37 | ||
38 | if reg: | |
39 | ||
40 | item = { | |
41 | 'port': reg.group(1), | |
42 | 'plugin': reg.group(2), | |
43 | 'ip': reg.group(3), | |
44 | 'login': reg.group(4), | |
45 | 'password': reg.group(5)} | |
46 | ||
47 | self.items.append(item) | |
48 | ||
49 | ||
50 | class HydraPlugin(PluginBase): | |
51 | """ | |
52 | Example plugin to parse hydra output. | |
53 | """ | |
54 | ||
55 | def __init__(self): | |
56 | super().__init__() | |
57 | self.id = "Hydra" | |
58 | self.name = "Hydra XML Output Plugin" | |
59 | self.plugin_version = "0.0.1" | |
60 | self.version = "7.5" | |
61 | 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).*?') | |
66 | self.host = None | |
67 | ||
68 | ||
69 | def parseOutputString(self, output, debug=False): | |
70 | """ | |
71 | This method will discard the output the shell sends, it will read it from | |
72 | the xml where it expects it to be present. | |
73 | ||
74 | NOTE: if 'debug' is true then it is being run from a test case and the | |
75 | output being sent is valid. | |
76 | """ | |
77 | ||
78 | parser = HydraParser(output) | |
79 | ||
80 | i = 0 | |
81 | hosts = {} | |
82 | service = '' | |
83 | port = '' | |
84 | ||
85 | for item in parser.items: | |
86 | ||
87 | service = item['plugin'] | |
88 | port = item['port'] | |
89 | ||
90 | if item['ip'] not in hosts: | |
91 | hosts[item['ip']] = [] | |
92 | ||
93 | hosts[item['ip']].append([item['login'], item['password']]) | |
94 | ||
95 | for k, v in hosts.items(): | |
96 | ||
97 | h_id = self.createAndAddHost(k) | |
98 | ||
99 | if self._isIPV4(k): | |
100 | ||
101 | i_id = self.createAndAddInterface( | |
102 | h_id, | |
103 | k, | |
104 | ipv4_address=k) | |
105 | ||
106 | else: | |
107 | i_id = self.createAndAddInterface( | |
108 | h_id, | |
109 | k, | |
110 | ipv6_address=k) | |
111 | ||
112 | s_id = self.createAndAddServiceToInterface( | |
113 | h_id, | |
114 | i_id, | |
115 | service, | |
116 | ports=[port], | |
117 | protocol="tcp", | |
118 | status="open") | |
119 | ||
120 | for cred in v: | |
121 | self.createAndAddCredToService( | |
122 | h_id, | |
123 | s_id, | |
124 | cred[0], | |
125 | cred[1]) | |
126 | ||
127 | self.createAndAddVulnToService( | |
128 | h_id, | |
129 | s_id, | |
130 | "Weak Credentials", | |
131 | "[hydra found the following credentials]\nuser:%s\npass:%s" % (cred[0], cred[1]), | |
132 | severity="high") | |
133 | ||
134 | del parser | |
135 | ||
136 | xml_arg_re = re.compile(r"^.*(-o\s*[^\s]+).*$") | |
137 | ||
138 | 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 | ||
144 | arg_match = self.xml_arg_re.match(command_string) | |
145 | ||
146 | if arg_match is None: | |
147 | return re.sub(r"(^.*?hydra?)", r"\1 -o %s" % self._output_file_path, command_string) | |
148 | else: | |
149 | return re.sub( | |
150 | arg_match.group(1), | |
151 | r"-o %s" % self._output_file_path, | |
152 | command_string) | |
153 | ||
154 | def _isIPV4(self, ip): | |
155 | if len(ip.split(".")) == 4: | |
156 | return True | |
157 | else: | |
158 | return False | |
159 | ||
160 | def setHost(self): | |
161 | pass | |
162 | ||
163 | ||
164 | def createPlugin(): | |
165 | return HydraPlugin() | |
166 | ||
167 | if __name__ == "__main__": | |
168 | import sys | |
169 | import os | |
170 | if len(sys.argv) == 2: | |
171 | report_file = sys.argv[1] | |
172 | if os.path.isfile(report_file): | |
173 | plugin = createPlugin() | |
174 | plugin.processReport(report_file) | |
175 | print(plugin.get_json()) | |
176 | else: | |
177 | print(f"Report not found: {report_file}") | |
178 | else: | |
179 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
180 | ||
181 | # 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 | # 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 | import re | |
7 | import os | |
8 | ||
9 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
10 | try: | |
11 | import xml.etree.cElementTree as ET | |
12 | import xml.etree.ElementTree as ET_ORIG | |
13 | ETREE_VERSION = ET_ORIG.VERSION | |
14 | except ImportError: | |
15 | import xml.etree.ElementTree as ET | |
16 | ETREE_VERSION = ET.VERSION | |
17 | ||
18 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] | |
19 | ||
20 | current_path = os.path.abspath(os.getcwd()) | |
21 | ||
22 | __author__ = "Francisco Amato" | |
23 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
24 | __credits__ = ["Francisco Amato"] | |
25 | __license__ = "" | |
26 | __version__ = "1.0.0" | |
27 | __maintainer__ = "Francisco Amato" | |
28 | __email__ = "[email protected]" | |
29 | __status__ = "Development" | |
30 | ||
31 | ||
32 | class ImpactXmlParser: | |
33 | """ | |
34 | The objective of this class is to parse an xml file generated by the impact tool. | |
35 | ||
36 | TODO: Handle errors. | |
37 | TODO: Test impact output version. Handle what happens if the parser doesn't support it. | |
38 | TODO: Test cases. | |
39 | ||
40 | @param impact_xml_filepath A proper xml generated by impact | |
41 | """ | |
42 | ||
43 | def __init__(self, xml_output): | |
44 | tree = self.parse_xml(xml_output) | |
45 | if tree: | |
46 | self.items = [data for data in self.get_items(tree)] | |
47 | else: | |
48 | self.items = [] | |
49 | ||
50 | def parse_xml(self, xml_output): | |
51 | """ | |
52 | Open and parse an xml file. | |
53 | ||
54 | TODO: Write custom parser to just read the nodes that we need instead of | |
55 | reading the whole file. | |
56 | ||
57 | @return xml_tree An xml tree instance. None if error. | |
58 | """ | |
59 | try: | |
60 | tree = ET.fromstring(xml_output) | |
61 | except SyntaxError as err: | |
62 | #logger.error("SyntaxError: %s. %s" % (err, xml_output)) | |
63 | return None | |
64 | ||
65 | return tree | |
66 | ||
67 | def get_items(self, tree): | |
68 | """ | |
69 | @return items A list of Host instances | |
70 | """ | |
71 | for node in tree.findall("entity/[@class='host']"): | |
72 | yield Item(node, tree) | |
73 | ||
74 | ||
75 | class Item: | |
76 | """ | |
77 | An abstract representation of a Item | |
78 | ||
79 | ||
80 | @param item_node A item_node taken from an impact xml tree | |
81 | """ | |
82 | ||
83 | def __init__(self, item_node, parent=None): | |
84 | self.node = item_node | |
85 | ||
86 | self.arch = self.get_text_from_subnode("property/[@key='arch']") | |
87 | ||
88 | self.host = self.get_text_from_subnode( | |
89 | "property/[@key='display_name']") | |
90 | ||
91 | self.ip = self.get_text_from_subnode("property/[@key='ip']") | |
92 | ||
93 | self.os = self.get_text_from_subnode( | |
94 | "property/[@key='os']/property/[@key='entity name']") | |
95 | ||
96 | self.ports = [] | |
97 | self.services = [] | |
98 | self.process_ports(item_node) | |
99 | self.process_services(item_node) | |
100 | ||
101 | self.agent = False | |
102 | ||
103 | for node in parent.findall("entity/[@class='agent']"): | |
104 | ||
105 | self.node = node | |
106 | agentip = node.get('name').split("/")[1] | |
107 | ||
108 | if self.ip == agentip: | |
109 | ||
110 | self.agentip = agentip | |
111 | ||
112 | self.ipfrom = self.get_text_from_subnode( | |
113 | "property/[@key='Connection Properties']/property/[@key='ip']") or agentip | |
114 | ||
115 | self.agentype = node.get("type") | |
116 | ||
117 | self.agentport = self.get_text_from_subnode( | |
118 | "property/[@key='Connection Properties']//property/[@key='port']") or "" | |
119 | ||
120 | self.agentsubtype = self.get_text_from_subnode( | |
121 | "property/[@key='Connection Properties']//property/[@key='subtype']") or "" | |
122 | ||
123 | self.agentcon = self.get_text_from_subnode( | |
124 | "property/[@key='Connection Properties']//property/[@key='type']") or "" | |
125 | ||
126 | self.agent = True | |
127 | break | |
128 | ||
129 | self.results = self.getResults(item_node) | |
130 | ||
131 | def process_ports(self, item_node): | |
132 | for p in item_node.findall("property/[@key='tcp_ports']/property/[@type='port']"): | |
133 | self.ports.append({'port': p.get('key'), | |
134 | 'protocol': "tcp", | |
135 | 'status': "open" if p.text == "listen" else p.text}) | |
136 | ||
137 | for p in item_node.findall("property/[@key='udp_ports']/property/[@type='port']"): | |
138 | self.ports.append({'port': p.get('key'), | |
139 | 'protocol': "udp", | |
140 | 'status': "open" if p.text == "listen" else p.text}) | |
141 | ||
142 | def process_services(self, item_node): | |
143 | for service in item_node.findall("property/[@key='services']/property"): | |
144 | service_name = service.get("key") | |
145 | port, protocol = service.findall('property')[0].get('key').split('-') | |
146 | self.services.append({ | |
147 | "name": service_name, | |
148 | "protocol": protocol, | |
149 | "port": port | |
150 | }) | |
151 | ||
152 | def getResults(self, tree): | |
153 | """ | |
154 | :param tree: | |
155 | """ | |
156 | for self.issues in tree.findall("property/[@key='Vulnerabilities']/property/[@type='container']"): | |
157 | yield Results(self.issues) | |
158 | # 2017R1 compatibility | |
159 | for self.issues in tree.findall("property/[@key='exposures']/property/[@type='container']"): | |
160 | yield Results(self.issues) | |
161 | ||
162 | def get_text_from_subnode(self, subnode_xpath_expr): | |
163 | """ | |
164 | Finds a subnode in the host node and the retrieves a value from it. | |
165 | ||
166 | @return An attribute value | |
167 | """ | |
168 | sub_node = self.node.find(subnode_xpath_expr) | |
169 | if sub_node is not None: | |
170 | return sub_node.text | |
171 | ||
172 | return None | |
173 | ||
174 | ||
175 | class Results(): | |
176 | ||
177 | def __init__(self, issue_node): | |
178 | self.node = issue_node | |
179 | self.ref = [issue_node.get("key")] | |
180 | self.severity = "" | |
181 | self.port = "Unknown" | |
182 | self.service_name = "n/a" | |
183 | self.protocol = "tcp?" | |
184 | vuln = issue_node.find("property/property") | |
185 | if not vuln: | |
186 | # 2017R1 compatibility | |
187 | self.ref = [] | |
188 | vuln = issue_node.find("property") | |
189 | self.name = self.get_text_from_subnode("property/[@key='title']") | |
190 | self.desc = self.get_text_from_subnode("property/[@key='description']") | |
191 | self.severity = self.get_text_from_subnode("property/[@key='severity']") | |
192 | self.service_name = self.get_text_from_subnode("property/[@key='service']") | |
193 | else: | |
194 | # 2013R3 xml version | |
195 | self.name = vuln.get("key") | |
196 | self.node = vuln | |
197 | self.desc = self.get_text_from_subnode("property/[@key='description']") | |
198 | self.port = self.get_text_from_subnode("property/[@key='port']") | |
199 | ||
200 | def get_text_from_subnode(self, subnode_xpath_expr): | |
201 | """ | |
202 | Finds a subnode in the host node and the retrieves a value from it. | |
203 | ||
204 | @return An attribute value | |
205 | """ | |
206 | sub_node = self.node.find(subnode_xpath_expr) | |
207 | if sub_node is not None: | |
208 | return sub_node.text | |
209 | ||
210 | return None | |
211 | ||
212 | ||
213 | class ImpactPlugin(PluginXMLFormat): | |
214 | """ | |
215 | Example plugin to parse impact output. | |
216 | """ | |
217 | ||
218 | def __init__(self): | |
219 | super().__init__() | |
220 | self.identifier_tag = "entities" | |
221 | self.id = "CoreImpact" | |
222 | self.name = "Core Impact XML Output Plugin" | |
223 | self.plugin_version = "0.0.2" | |
224 | self.version = "Core Impact 2013R1/2017R2" | |
225 | self.framework_version = "1.0.0" | |
226 | self.options = None | |
227 | self._current_output = None | |
228 | self._command_regex = re.compile(r'^(sudo impact|\.\/impact).*?') | |
229 | ||
230 | def parseOutputString(self, output, debug=False): | |
231 | parser = ImpactXmlParser(output) | |
232 | mapped_services = {} | |
233 | mapped_ports = {} | |
234 | for item in parser.items: | |
235 | ||
236 | h_id = self.createAndAddHost( | |
237 | item.ip, | |
238 | item.os + " " + item.arch) | |
239 | ||
240 | i_id = self.createAndAddInterface( | |
241 | h_id, | |
242 | item.ip, | |
243 | ipv4_address=item.ip, | |
244 | hostname_resolution=[item.host]) | |
245 | ||
246 | for service in item.services: | |
247 | s_id = self.createAndAddServiceToInterface( | |
248 | h_id, | |
249 | i_id, | |
250 | service['name'], | |
251 | service['protocol'], | |
252 | ports=[service['port']], | |
253 | status='open') | |
254 | mapped_services[service['name']] = s_id | |
255 | mapped_ports[service['port']] = s_id | |
256 | ||
257 | if item.agent: | |
258 | desc = "Agent Type: " + item.agentype | |
259 | desc += "\nConn from:" + item.ipfrom | |
260 | desc += "\nPort:" + item.agentport | |
261 | desc += "\nProtocol:" + item.agentsubtype | |
262 | desc += "\nConn:" + item.agentcon | |
263 | ||
264 | self.createAndAddVulnToHost( | |
265 | h_id, | |
266 | "Core Impact Agent", | |
267 | desc=desc, | |
268 | severity="HIGH") | |
269 | ||
270 | for v in item.results: | |
271 | if v.service_name == "n/a" and v.port == "Unknown": | |
272 | self.createAndAddVulnToHost( | |
273 | h_id, | |
274 | v.name, | |
275 | desc=v.desc, | |
276 | severity=v.severity, | |
277 | ref=v.ref) | |
278 | else: | |
279 | s_id = mapped_services.get(v.service_name) or mapped_ports.get(v.port) | |
280 | print(v.service_name) | |
281 | print(s_id) | |
282 | self.createAndAddVulnToService( | |
283 | h_id, | |
284 | s_id, | |
285 | v.name, | |
286 | desc=v.desc, | |
287 | severity=v.severity, | |
288 | ref=v.ref) | |
289 | ||
290 | for p in item.ports: | |
291 | s_id = self.createAndAddServiceToInterface( | |
292 | h_id, | |
293 | i_id, | |
294 | p['port'], | |
295 | p['protocol'], | |
296 | ports=[p['port']], | |
297 | status=p['status']) | |
298 | del parser | |
299 | ||
300 | def processCommandString(self, username, current_path, command_string): | |
301 | return None | |
302 | ||
303 | def setHost(self): | |
304 | pass | |
305 | ||
306 | ||
307 | def createPlugin(): | |
308 | return ImpactPlugin() | |
309 | ||
310 | if __name__ == "__main__": | |
311 | import sys | |
312 | import os | |
313 | if len(sys.argv) == 2: | |
314 | report_file = sys.argv[1] | |
315 | if os.path.isfile(report_file): | |
316 | plugin = createPlugin() | |
317 | plugin.processReport(report_file) | |
318 | print(plugin.get_json()) | |
319 | else: | |
320 | print(f"Report not found: {report_file}") | |
321 | else: | |
322 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
323 | ||
324 | # I'm Py3 |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2018 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) 2018 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | """ | |
5 | import csv | |
6 | from io import StringIO | |
7 | from faraday_plugins.plugins.plugin import PluginBase | |
8 | ||
9 | ||
10 | def calculate_severity(number): | |
11 | if number is None: | |
12 | return "info" | |
13 | number = float(number) | |
14 | # Based in CVSS V2 | |
15 | if 0 <= number < 4.0: | |
16 | return "low" | |
17 | elif 4.0 <= number < 7.0: | |
18 | return "med" | |
19 | elif 7.0 <= number <= 10: | |
20 | return "high" | |
21 | ||
22 | ||
23 | class Ip360Parser: | |
24 | ||
25 | def __init__(self, csv_content): | |
26 | self.csv_content = StringIO(csv_content.decode('ascii', 'ignore')) | |
27 | self.csv_reader = csv.DictReader(self.csv_content, delimiter=',', quotechar='"') | |
28 | ||
29 | def parse(self): | |
30 | ||
31 | result = [] | |
32 | for row in self.csv_reader: | |
33 | ||
34 | host = { | |
35 | "name": row.get("IP"), | |
36 | "os": row.get("OS") | |
37 | } | |
38 | ||
39 | interface = { | |
40 | "name": row.get("IP"), | |
41 | "hostname_resolution": [row.get("NetBIOS Name")], | |
42 | "network_segment": row.get("NetBIOS Domain"), | |
43 | } | |
44 | ||
45 | service = {"port": row.get("Port")} | |
46 | ||
47 | vulnerability = { | |
48 | "name": row.get("Vulnerability"), | |
49 | "description": row.get("Description"), | |
50 | "resolution": row.get("Remediation"), | |
51 | "ref": [ | |
52 | row.get("CVE"), | |
53 | "Vuln ID: " + row.get("Vulnerability ID"), | |
54 | "Risk: " + row.get("Risk"), | |
55 | "Skill: " + row.get("Skill"), | |
56 | "CVSS V2: " + row.get("CVSS V2"), | |
57 | "CVSS V3: " + row.get("CVSS V3")], | |
58 | "severity": row.get("CVSS V2") | |
59 | } | |
60 | ||
61 | result.append((host, interface, service, vulnerability)) | |
62 | ||
63 | return result | |
64 | ||
65 | class Ip360Plugin(PluginBase): | |
66 | """ | |
67 | Example plugin to parse Ip360 output. | |
68 | """ | |
69 | ||
70 | def __init__(self): | |
71 | super().__init__() | |
72 | self.id = "Ip360" | |
73 | self.name = "Ip360 CSV Output Plugin" | |
74 | self.plugin_version = "0.0.1" | |
75 | self.options = None | |
76 | ||
77 | def parseOutputString(self, output, debug=False): | |
78 | ||
79 | parser = Ip360Parser(output) | |
80 | for host, interface, service, vulnerability in parser.parse(): | |
81 | ||
82 | h_id = self.createAndAddHost(host.get("name"), host.get("os")) | |
83 | ||
84 | i_id = self.createAndAddInterface( | |
85 | h_id, | |
86 | interface.get("name"), | |
87 | ipv4_address=interface.get("name"), | |
88 | hostname_resolution=interface.get("hostname_resolution"), | |
89 | network_segment=interface.get("network_segment")) | |
90 | ||
91 | ||
92 | if service.get("port") == "-": | |
93 | port = "0" | |
94 | protocol = "unknown" | |
95 | else: | |
96 | port = service.get("port").split("/")[0] | |
97 | protocol = service.get("port").split("/")[1] | |
98 | ||
99 | s_id = self.createAndAddServiceToInterface( | |
100 | h_id, | |
101 | i_id, | |
102 | service.get("port"), | |
103 | protocol=protocol, | |
104 | ports=[port]) | |
105 | ||
106 | self.createAndAddVulnToService( | |
107 | h_id, | |
108 | s_id, | |
109 | vulnerability.get("name"), | |
110 | desc=vulnerability.get("description"), | |
111 | resolution=vulnerability.get("resolution"), | |
112 | severity=calculate_severity(vulnerability.get("severity")), | |
113 | ref=vulnerability.get("ref")) | |
114 | ||
115 | def createPlugin(): | |
116 | return Ip360Plugin() | |
117 | ||
118 | if __name__ == "__main__": | |
119 | import sys | |
120 | import os | |
121 | if len(sys.argv) == 2: | |
122 | report_file = sys.argv[1] | |
123 | if os.path.isfile(report_file): | |
124 | plugin = createPlugin() | |
125 | plugin.processReport(report_file) | |
126 | print(plugin.get_json()) | |
127 | else: | |
128 | print(f"Report not found: {report_file}") | |
129 | else: | |
130 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
131 | # 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 | # 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 | import os | |
7 | from lxml import etree | |
8 | from faraday_plugins.plugins.plugin import PluginBase | |
9 | ||
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 | |
18 | ||
19 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] | |
20 | ||
21 | current_path = os.path.abspath(os.getcwd()) | |
22 | ||
23 | __author__ = "Thierry Beauquier" | |
24 | __license__ = "" | |
25 | __version__ = "1.0.0" | |
26 | __maintainer__ = "Thierry Beauquier" | |
27 | __email__ = "[email protected]" | |
28 | __status__ = "Development" | |
29 | ||
30 | """ | |
31 | This plugin has been designed to be used with python-unittest2/paramiko script to perform security compliancy verification. It enables to have displayed both security scans results (nmap, | |
32 | nessus, ..) and security verification compliancy (CIS-CAT, compagny's product security requirement) by Faraday-IPE | |
33 | ||
34 | This plugin requires that a element "host" is added to <testcase/> (sed -i 's/<testcase/<testcase host=\"192.168.1.1\"/' junit.xml) | |
35 | ||
36 | <testsuite errors="0" failures="1" name="AccountsWithSuperuserPrivilegesShallBeDisabledByDefault-20170118090010" skipped="0" tests="1" time="0.144"> | |
37 | <testcase host="192.168.1.1" classname="AccountsWithSuperuserPrivilegesShallBeDisabledByDefault" name="test_sshdRootLogin" time="0.144"> | |
38 | <failure message="SSH for root account is not disabled: '' matches '' in ''" type="AssertionError"> | |
39 | <![CDATA[Traceback (most recent call last): | |
40 | File "bsr-ci.py", line 514, in test_sshdRootLogin | |
41 | self.assertNotRegexpMatches(_ssh('cat /etc/ssh/sshd_config | egrep "^PermitRootLogin" | awk \'{print $2}\' | egrep "no|No|NO"',host),'', 'SSH for root account is not disabled') | |
42 | AssertionError: SSH for root account is not disabled: '' matches '' in '' | |
43 | ]]> </failure> | |
44 | </testcase> | |
45 | <system-out> | |
46 | <![CDATA[]]> </system-out> | |
47 | <system-err> | |
48 | <![CDATA[]]> </system-err> | |
49 | </testsuite> | |
50 | ||
51 | ||
52 | """ | |
53 | ||
54 | ||
55 | class JunitXmlParser: | |
56 | """ | |
57 | The objective of this class is to parse an xml file generated by the junit. | |
58 | ||
59 | @param junit_xml_filepath A proper xml generated by junit | |
60 | """ | |
61 | ||
62 | def __init__(self, xml_output): | |
63 | ||
64 | tree = self.parse_xml(xml_output) | |
65 | if tree: | |
66 | self.items = [data for data in self.get_items(tree)] | |
67 | else: | |
68 | self.items = [] | |
69 | ||
70 | def parse_xml(self, xml_output): | |
71 | """ | |
72 | Open and parse an xml file. | |
73 | ||
74 | @return xml_tree An xml tree instance. None if error. | |
75 | """ | |
76 | try: | |
77 | tree = etree.fromstring(xml_output) | |
78 | except SyntaxError as err: | |
79 | print("SyntaxError: %s. %s" % (err, xml_output)) | |
80 | return None | |
81 | return tree | |
82 | ||
83 | def get_items(self, tree): | |
84 | """ | |
85 | @return items A list of Failure instances | |
86 | """ | |
87 | ||
88 | for node in tree.findall('testsuite/testcase/failure'): | |
89 | yield Testsuite(node) | |
90 | ||
91 | ||
92 | class Testsuite: | |
93 | ||
94 | def __init__(self, testsuite_node): | |
95 | self.node = testsuite_node | |
96 | ||
97 | self.parent = self.node.getparent() | |
98 | self.name = self.parent.get('name') | |
99 | self.host = self.parent.get('host') | |
100 | if self.host is None: | |
101 | print('host element is missing') | |
102 | self.host = '' | |
103 | ||
104 | self.message = self.get_text_from_subnode('message') | |
105 | ||
106 | def get_text_from_subnode(self, subnode_xpath_expr): | |
107 | """ | |
108 | Finds a subnode in the host node and the retrieves a value from it. | |
109 | ||
110 | @return An attribute value | |
111 | """ | |
112 | sub_node = self.node.get(subnode_xpath_expr) | |
113 | if sub_node is not None: | |
114 | return sub_node | |
115 | ||
116 | return None | |
117 | ||
118 | ||
119 | class JunitPlugin(PluginBase): | |
120 | """ | |
121 | Example plugin to parse junit output. | |
122 | """ | |
123 | ||
124 | def __init__(self): | |
125 | super().__init__() | |
126 | self.id = "Junit" | |
127 | self.name = "Junit XML Output Plugin" | |
128 | self.plugin_version = "0.0.1" | |
129 | self.version = "" | |
130 | self.framework_version = "1.0.0" | |
131 | self.options = None | |
132 | self._current_output = None | |
133 | self._command_regex = None | |
134 | ||
135 | def parseOutputString(self, output, debug=False): | |
136 | ||
137 | parser = JunitXmlParser(output) | |
138 | for item in parser.items: | |
139 | h_id = self.createAndAddHost(item.host, os="Linux") | |
140 | i_id = self.createAndAddInterface(h_id, item.host, ipv4_address=item.host) | |
141 | self.createAndAddVulnToHost(h_id, name=item.name, desc=item.message, ref=[], severity="High") | |
142 | del parser | |
143 | ||
144 | ||
145 | def createPlugin(): | |
146 | return JunitPlugin() | |
147 | ||
148 | ||
149 | if __name__ == "__main__": | |
150 | import sys | |
151 | import os | |
152 | if len(sys.argv) == 2: | |
153 | report_file = sys.argv[1] | |
154 | if os.path.isfile(report_file): | |
155 | plugin = createPlugin() | |
156 | plugin.processReport(report_file) | |
157 | print(plugin.get_json()) | |
158 | else: | |
159 | print(f"Report not found: {report_file}") | |
160 | else: | |
161 | print(f"USAGE {sys.argv[0]} REPORT_FILE") |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2017 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | """ | |
6 | # I'm Py3⏎ |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2017 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | """ | |
5 | import re | |
6 | import os | |
7 | from collections import defaultdict | |
8 | ||
9 | from faraday_plugins.plugins.plugin import PluginByExtension | |
10 | from faraday_plugins.plugins.plugins_utils import filter_services, get_all_protocols | |
11 | ||
12 | ||
13 | current_path = os.path.abspath(os.getcwd()) | |
14 | ||
15 | ||
16 | class LynisLogDataExtracter(): | |
17 | def __init__(self, datfile=None, output=None): | |
18 | 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 | |
25 | ||
26 | def _svcHelper(self, ip, port, protocol, name): | |
27 | self.services[ip].append({'port': port, 'protocol': protocol, 'name': name}) | |
28 | ||
29 | def hostname(self): | |
30 | hostname_match = re.search('^hostname=(.+)$', self.rawcontents, re.MULTILINE) | |
31 | hostname = hostname_match.group(1).strip() | |
32 | domain_match = re.search('^domainname=(.+)$', self.rawcontents, re.MULTILINE) | |
33 | if domain_match: | |
34 | domain = domain_match.group(1).strip() | |
35 | return ".".join([hostname,domain]) | |
36 | else: | |
37 | return hostname | |
38 | ||
39 | def osfullname(self): | |
40 | name_match = re.search('^os_name=(.+)$', self.rawcontents, re.MULTILINE) | |
41 | name = name_match.group(1).strip() | |
42 | version_match = re.search('^os_version=(.+)$', self.rawcontents, re.MULTILINE) | |
43 | version = version_match.group(1).strip() | |
44 | return " ".join([name, version]) | |
45 | ||
46 | def ipv4(self): | |
47 | ipv4addrs = [] | |
48 | ipv4s = re.findall('^network_ipv4_address\[\]=(.+)$', | |
49 | self.rawcontents, re.MULTILINE) | |
50 | ipv4addrs = self.ipv4_filter(ipv4s) | |
51 | return(ipv4addrs) | |
52 | ||
53 | def ipv6(self): | |
54 | ipv6addrs = [] | |
55 | ipv6s = re.findall('^network_ipv6_address\[\]=(.+)$', | |
56 | self.rawcontents, re.MULTILINE) | |
57 | ipv6addrs = self.ipv6_filter(ipv6s) | |
58 | return(ipv6addrs) | |
59 | ||
60 | def ipv4_filter(self, ips): | |
61 | ip_list = [] | |
62 | for ip in ips: | |
63 | if not ip == "127.0.0.1": | |
64 | ip_list.append(ip) | |
65 | ||
66 | return ip_list | |
67 | ||
68 | def ipv6_filter(self, ips): | |
69 | ip_list = [] | |
70 | for ip in ips: | |
71 | if not ip.startswith('fe80') and not ip.startswith('::1'): | |
72 | ip_list.append(ip) | |
73 | ||
74 | return ip_list | |
75 | ||
76 | def kernelVersion(self): | |
77 | versions_dict = {} | |
78 | ||
79 | version = re.search('^os_kernel_version=(.+)$', | |
80 | self.rawcontents, re.MULTILINE) | |
81 | if version: | |
82 | versions_dict['Kernel Version'] = version.group(1).strip() | |
83 | ||
84 | version_full = re.search('^os_kernel_version_full=(.+)$', | |
85 | self.rawcontents, re.MULTILINE) | |
86 | if version_full: | |
87 | versions_dict['Kernel Version Full'] = version_full.group(1).strip() | |
88 | ||
89 | return versions_dict | |
90 | ||
91 | def listeningservices(self): | |
92 | line = re.findall('^network_listen_port\[\]=(.+)$', | |
93 | self.rawcontents, re.MULTILINE) | |
94 | # To avoid local services, we will create the following list | |
95 | local_services = ['*', 'localhost'] | |
96 | ||
97 | for combo in line: | |
98 | elements = self.clean_services(combo, local_services) | |
99 | if elements is not None: | |
100 | self._svcHelper(elements['ip'], | |
101 | elements['port'], | |
102 | elements['protocol'], | |
103 | elements['name']) | |
104 | return self.services | |
105 | ||
106 | def clean_services(self, combo, local_services): | |
107 | add = False | |
108 | #if "localhost" in combo: | |
109 | if combo.count("|") > 1: | |
110 | # Service with url, protocol and perhaps name | |
111 | items_service = combo.split('|') | |
112 | if not self.local_service(items_service, local_services): | |
113 | # self.aux_items will be an auxiliar list. We will use it... | |
114 | # ...for poping the url and the protocol so that the last element... | |
115 | # ... of the list, will be the name of the service | |
116 | self.aux_items = list(filter(None, items_service)) | |
117 | elements_ip_port, count = self.get_ip_and_port(self.aux_items, remove_from_list=True) | |
118 | protocol = self.get_protocol() | |
119 | name = self.aux_items[0] | |
120 | add = True | |
121 | ||
122 | if name == '-': | |
123 | details = self.search_service(elements_ip_port[1]) | |
124 | name = details['name'] | |
125 | elif combo.count('|') == 1: | |
126 | # Service only with url | |
127 | items_service = combo.split('|') | |
128 | if not self.local_service(items_service, local_services): | |
129 | elements_ip_port, count = self.get_ip_and_port(items_service) | |
130 | details = self.search_service(elements_ip_port[1]) | |
131 | protocol = details['protocol'] | |
132 | name = details['name'] | |
133 | add = True | |
134 | else: | |
135 | items_service = combo | |
136 | count = items_service.count(':') | |
137 | elements_ip_port = items_service.split(':') | |
138 | details = self.search_service(elements_ip_port[1]) | |
139 | protocol = details['protocol'] | |
140 | name = details['name'] | |
141 | add = True | |
142 | ||
143 | if add: | |
144 | ip, port = self.colon_count(count, elements_ip_port, items_service) | |
145 | elements_dict = { | |
146 | "ip":ip, | |
147 | "port": port, | |
148 | "protocol": protocol, | |
149 | "name": name | |
150 | } | |
151 | return elements_dict | |
152 | else: | |
153 | return None | |
154 | ||
155 | def local_service(self, service_data, local_services): | |
156 | ip = self.get_ip_and_port(service_data)[0][0] | |
157 | local = True | |
158 | if ip not in local_services and not ip.startswith(':'): | |
159 | local = False | |
160 | ||
161 | return local | |
162 | ||
163 | def get_ip_and_port(self, service_data, remove_from_list=False): | |
164 | url_data = [url for url in service_data if ':' in url][0] | |
165 | count = url_data.count(':') | |
166 | ip_port = url_data.split(':') | |
167 | ||
168 | if remove_from_list: | |
169 | self.aux_items.remove(url_data) | |
170 | ||
171 | return ip_port, count | |
172 | ||
173 | def get_protocol(self): | |
174 | # network_listen_port variables are different in .log and .dat reports | |
175 | # .log: tcp4|127.0.0.1:5985|zabbix_age| | |
176 | # .dat: 127.0.0.1:5985|tcp4|zabbix_age| | |
177 | # This method will check if the protocol (from the function get_all_protocols()) | |
178 | # matches with the protocol that network_listen_port contains | |
179 | protocols = get_all_protocols() | |
180 | for item in protocols: | |
181 | protocol = [p for p in self.aux_items if item in p.lower()] | |
182 | if protocol: | |
183 | self.aux_items.remove(protocol[0]) | |
184 | return protocol[0] | |
185 | ||
186 | def search_service(self, port): | |
187 | srv = filter_services() | |
188 | details_dict = { | |
189 | 'name' : 'Unknown', | |
190 | 'protocol' : 'Unknown' | |
191 | } | |
192 | for item in srv: | |
193 | service_tuple = item[0].split('/') | |
194 | parsed_port = service_tuple[0] | |
195 | if parsed_port == port: | |
196 | details_dict['name'] = item[1] | |
197 | details_dict['protocol'] = service_tuple[1] | |
198 | return details_dict | |
199 | return details_dict | |
200 | ||
201 | def colon_count(self, count, elements_ip_port, items_service): | |
202 | #Ipv4 | |
203 | if count == 1: | |
204 | ip, port = elements_ip_port | |
205 | ||
206 | #Ipv6 | |
207 | elif count == 3: | |
208 | port = elements_ip_port[3] | |
209 | ip = '::' | |
210 | ||
211 | #Ipv6 | |
212 | elif count == 5: | |
213 | port = elements_ip_port[5] | |
214 | ip = items_service[0].replace(':{}'.format(port), '') | |
215 | ||
216 | return ip, port | |
217 | ||
218 | def parse_suggestions(self): | |
219 | sugs = {} | |
220 | m = re.findall('^suggestion\[\]=(.+)$', self.rawcontents, re.MULTILINE) | |
221 | for combo in m: | |
222 | x = combo.split('|') | |
223 | sugs[x[0]] = x[1] | |
224 | return(sugs) | |
225 | ||
226 | def parse_warnings(self): | |
227 | warns = {} | |
228 | m = re.findall('^warning\[\]=(.+)$', self.rawcontents, re.MULTILINE) | |
229 | for combo in m: | |
230 | x = combo.split('|') | |
231 | warns[x[0]] = x[1] | |
232 | return(warns) | |
233 | ||
234 | ||
235 | class LynisPlugin(PluginByExtension): | |
236 | """ Simple example plugin to parse lynis' lynis-report.dat file.""" | |
237 | ||
238 | def __init__(self): | |
239 | super().__init__() | |
240 | self.extension = [".dat", ".log"] | |
241 | self.id = "Lynis" | |
242 | self.name = "Lynis DAT Output Plugin" | |
243 | self.plugin_version = "0.4" | |
244 | self.version = "2.7.1" | |
245 | self.options = None | |
246 | self._current_output = None | |
247 | rr = r'^(lynis|sudo lynis|\.\/lynis|sudo \.\/lynis).*?' | |
248 | self._command_regex = re.compile(rr) | |
249 | self._hosts = [] | |
250 | ||
251 | global current_path | |
252 | ||
253 | def report_belongs_to(self, **kwargs): | |
254 | if super().report_belongs_to(**kwargs): | |
255 | report_path = kwargs.get("report_path", "") | |
256 | with open(report_path) as f: | |
257 | output = f.read() | |
258 | return output.startswith("# Lynis Report") | |
259 | return False | |
260 | ||
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) | |
268 | hostname = lde.hostname() | |
269 | ipv4s = lde.ipv4() | |
270 | ipv6s = lde.ipv6() | |
271 | kernel_versions = lde.kernelVersion() | |
272 | services = lde.listeningservices() | |
273 | suggestions = lde.parse_suggestions() | |
274 | warnings = lde.parse_warnings() | |
275 | ||
276 | for ipv4 in ipv4s: | |
277 | h_id = self.createAndAddHost(name=ipv4, | |
278 | os=lde.osfullname(), | |
279 | hostnames=[hostname]) | |
280 | ||
281 | self.create_services(h_id, services, ipv4) | |
282 | self.create_vulns_with_kernel(h_id, kernel_versions) | |
283 | self.create_vulns_with_suggestions(h_id, suggestions) | |
284 | self.create_vulns_with_warns(h_id, warnings) | |
285 | ||
286 | for ipv6 in ipv6s: | |
287 | h_id = self.createAndAddHost(name=ipv6, | |
288 | os=lde.osfullname(), | |
289 | hostnames=[hostname]) | |
290 | ||
291 | self.create_services(h_id, services, ipv6) | |
292 | self.create_vulns_with_kernel(h_id, kernel_versions) | |
293 | self.create_vulns_with_suggestions(h_id, suggestions) | |
294 | self.create_vulns_with_warns(h_id, warnings) | |
295 | ||
296 | def create_services(self, host_id, parsed_services, ip_version): | |
297 | for service_data in parsed_services[ip_version]: | |
298 | self.createAndAddServiceToHost(host_id=host_id, | |
299 | name=service_data['name'], | |
300 | protocol=service_data['protocol'], | |
301 | ports=[service_data['port']]) | |
302 | ||
303 | if '0.0.0.0' in parsed_services: | |
304 | for service_data in parsed_services['0.0.0.0']: | |
305 | self.createAndAddServiceToHost(host_id=host_id, | |
306 | name=service_data['name'], | |
307 | protocol=service_data['protocol'], | |
308 | ports=[service_data['port']]) | |
309 | ||
310 | def create_vulns_with_kernel(self, host_id, kernel_versions): | |
311 | for kernel, version in kernel_versions.items(): | |
312 | self.createAndAddVulnToHost( | |
313 | host_id=host_id, | |
314 | name=kernel, | |
315 | severity='info', | |
316 | desc=version | |
317 | ) | |
318 | ||
319 | def create_vulns_with_suggestions(self, host_id, sugs): | |
320 | for sug in sugs: | |
321 | self.createAndAddVulnToHost( | |
322 | host_id=host_id, | |
323 | name=sug, | |
324 | severity='med', | |
325 | desc=sugs[sug] | |
326 | ) | |
327 | ||
328 | def create_vulns_with_warns(self, host_id, warns): | |
329 | for warn in warns: | |
330 | self.createAndAddVulnToHost( | |
331 | host_id=host_id, | |
332 | name=warn, | |
333 | severity='high', | |
334 | desc=warns[warn] | |
335 | ) | |
336 | ||
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()) | |
351 | ||
352 | ||
353 | def createPlugin(): | |
354 | return LynisPlugin() | |
355 | ||
356 | if __name__ == "__main__": | |
357 | import sys | |
358 | import os | |
359 | if len(sys.argv) == 2: | |
360 | report_file = sys.argv[1] | |
361 | if os.path.isfile(report_file): | |
362 | plugin = createPlugin() | |
363 | plugin.processReport(report_file) | |
364 | print(plugin.get_json()) | |
365 | else: | |
366 | print(f"Report not found: {report_file}") | |
367 | else: | |
368 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
369 | # I'm Py3 |
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> |
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) 2015 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | """ | |
5 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
6 | ||
7 | import zipfile | |
8 | import re | |
9 | import os | |
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 | |
18 | ||
19 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] | |
20 | ||
21 | current_path = os.path.abspath(os.getcwd()) | |
22 | ||
23 | __author__ = "Ezequiel Tavella" | |
24 | __copyright__ = "Copyright (c) 2015, Infobyte LLC" | |
25 | __credits__ = ["Ezequiel Tavella"] | |
26 | __license__ = "" | |
27 | __version__ = "1.0.1" | |
28 | __maintainer__ = "Ezequiel Tavella" | |
29 | __status__ = "Development" | |
30 | ||
31 | ||
32 | def openMtgx(mtgx_file): | |
33 | ||
34 | 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 | return None | |
41 | ||
42 | file.close() | |
43 | return xml | |
44 | ||
45 | ||
46 | class Host(): | |
47 | ||
48 | def __init__(self): | |
49 | self.ip = "" | |
50 | self.node_id = "" | |
51 | self.dns_name = "" | |
52 | self.website = "" | |
53 | self.netblock = "" | |
54 | self.location = "" | |
55 | self.mx_record = "" | |
56 | self.ns_record = "" | |
57 | ||
58 | ||
59 | class MaltegoMtgxParser(): | |
60 | ||
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 = {} | |
75 | ||
76 | def getRelations(self): | |
77 | """ | |
78 | Get relations between nodes. | |
79 | Two ways: Source-> Target | |
80 | Source <- Target | |
81 | """ | |
82 | for edge in self.edges: | |
83 | ||
84 | source = edge.get("source") | |
85 | target = edge.get("target") | |
86 | ||
87 | if source not in self.relations: | |
88 | self.relations.update({source: [target]}) | |
89 | ||
90 | if target not in self.relations: | |
91 | self.relations.update({target: [source]}) | |
92 | ||
93 | values = self.relations[source] | |
94 | values.append(target) | |
95 | self.relations.update({source: values}) | |
96 | ||
97 | values = self.relations[target] | |
98 | values.append(source) | |
99 | self.relations.update({target: values}) | |
100 | ||
101 | def getIpAndId(self, node): | |
102 | ||
103 | # Find node ID and maltego entity | |
104 | node_id = node.get("id") | |
105 | entity = node.find( | |
106 | "{http://graphml.graphdrawing.org/xmlns}data/" | |
107 | "{http://maltego.paterva.com/xml/mtgx}MaltegoEntity") | |
108 | ||
109 | # Check if is IPv4Address | |
110 | if entity.get("type") != "maltego.IPv4Address": | |
111 | return None | |
112 | ||
113 | # Get IP value | |
114 | value = entity.find( | |
115 | "{http://maltego.paterva.com/xml/mtgx}Properties/" | |
116 | "{http://maltego.paterva.com/xml/mtgx}Property/" | |
117 | "{http://maltego.paterva.com/xml/mtgx}Value") | |
118 | ||
119 | return {"node_id": node_id, "ip": value.text} | |
120 | ||
121 | def getNode(self, node_id): | |
122 | ||
123 | # Get node, filter by id | |
124 | for node in self.nodes: | |
125 | ||
126 | if node.get("id") == node_id: | |
127 | return node | |
128 | ||
129 | def getType(self, node): | |
130 | ||
131 | # Get type of this node | |
132 | entity = node.find( | |
133 | "{http://graphml.graphdrawing.org/xmlns}data/" | |
134 | "{http://maltego.paterva.com/xml/mtgx}MaltegoEntity") | |
135 | ||
136 | return entity.get("type") | |
137 | ||
138 | def getWebsite(self, target_node): | |
139 | ||
140 | # Parse Website Entity | |
141 | result = {"name": "", "ssl_enabled": "", "urls": ""} | |
142 | ||
143 | props = target_node.find( | |
144 | "{http://graphml.graphdrawing.org/xmlns}data/" | |
145 | "{http://maltego.paterva.com/xml/mtgx}MaltegoEntity/" | |
146 | "{http://maltego.paterva.com/xml/mtgx}Properties") | |
147 | ||
148 | for prop in props: | |
149 | ||
150 | name_property = prop.get("name") | |
151 | value = prop.find( | |
152 | "{http://maltego.paterva.com/xml/mtgx}Value").text | |
153 | ||
154 | if name_property == "fqdn": | |
155 | result["name"] = value | |
156 | elif name_property == "website.ssl-enabled": | |
157 | result["ssl_enabled"] = value | |
158 | elif name_property == "URLS": | |
159 | result["urls"] = value | |
160 | ||
161 | return result | |
162 | ||
163 | def getNetBlock(self, target_node): | |
164 | ||
165 | # Parse Netblock Entity | |
166 | result = {"ipv4_range": "", "network_owner": "", "country": ""} | |
167 | ||
168 | props = target_node.find( | |
169 | "{http://graphml.graphdrawing.org/xmlns}data/" | |
170 | "{http://maltego.paterva.com/xml/mtgx}MaltegoEntity/" | |
171 | "{http://maltego.paterva.com/xml/mtgx}Properties") | |
172 | ||
173 | for prop in props: | |
174 | ||
175 | name_property = prop.get("name") | |
176 | value = prop.find( | |
177 | "{http://maltego.paterva.com/xml/mtgx}Value").text | |
178 | ||
179 | if name_property == "ipv4-range": | |
180 | result["ipv4_range"] = value | |
181 | elif name_property == "description": | |
182 | result["network_owner"] = value | |
183 | elif name_property == "country": | |
184 | result["country"] = value | |
185 | ||
186 | return result | |
187 | ||
188 | def getLocation(self, target_node): | |
189 | ||
190 | # Parse Location Entity | |
191 | result = { | |
192 | "name": "", | |
193 | "area": "", | |
194 | "country_code": "", | |
195 | "longitude": "", | |
196 | "latitude": "", | |
197 | "area_2": ""} | |
198 | ||
199 | # Get relations with other nodes | |
200 | node_relations = self.relations[target_node.get("id")] | |
201 | ||
202 | # Find location node based in relation with netblock node. | |
203 | located = False | |
204 | for node_id in node_relations: | |
205 | ||
206 | target_node = self.getNode(node_id) | |
207 | if self.getType(target_node) == "maltego.Location": | |
208 | located = True | |
209 | break | |
210 | ||
211 | if not located: | |
212 | return None | |
213 | ||
214 | # Get properties and update data | |
215 | props = target_node.find( | |
216 | "{http://graphml.graphdrawing.org/xmlns}data/" | |
217 | "{http://maltego.paterva.com/xml/mtgx}MaltegoEntity/" | |
218 | "{http://maltego.paterva.com/xml/mtgx}Properties") | |
219 | ||
220 | for prop in props: | |
221 | ||
222 | name_property = prop.get("name") | |
223 | value = prop.find( | |
224 | "{http://maltego.paterva.com/xml/mtgx}Value").text | |
225 | ||
226 | if name_property == "location.name": | |
227 | result["name"] = value | |
228 | elif name_property == "location.area": | |
229 | result["area"] = value | |
230 | elif name_property == "countrycode": | |
231 | result["country_code"] = value | |
232 | elif name_property == "longitude": | |
233 | result["longitude"] = value | |
234 | elif name_property == "latitude": | |
235 | result["latitude"] = value | |
236 | elif name_property == "area": | |
237 | result["area_2"] = value | |
238 | ||
239 | return result | |
240 | ||
241 | def getValue(self, target_node): | |
242 | ||
243 | # Parse Entity | |
244 | result = {"value": ""} | |
245 | ||
246 | value = target_node.find( | |
247 | "{http://graphml.graphdrawing.org/xmlns}data/" | |
248 | "{http://maltego.paterva.com/xml/mtgx}MaltegoEntity/" | |
249 | "{http://maltego.paterva.com/xml/mtgx}Properties/" | |
250 | "{http://maltego.paterva.com/xml/mtgx}Property/" | |
251 | "{http://maltego.paterva.com/xml/mtgx}Value") | |
252 | ||
253 | result["value"] = value.text | |
254 | return result | |
255 | ||
256 | def parse(self): | |
257 | ||
258 | self.getRelations() | |
259 | ||
260 | for node in self.nodes: | |
261 | ||
262 | # Get IP Address if not continue with other node... | |
263 | result = self.getIpAndId(node) | |
264 | if not result: | |
265 | continue | |
266 | ||
267 | # Create host with values by default | |
268 | host = Host() | |
269 | host.ip = result["ip"] | |
270 | host.node_id = result["node_id"] | |
271 | ||
272 | # Get relations with other nodes | |
273 | node_relations = self.relations[host.node_id] | |
274 | ||
275 | for node_id in node_relations: | |
276 | ||
277 | # Get target node and type of node. | |
278 | target_node = self.getNode(node_id) | |
279 | target_type = self.getType(target_node) | |
280 | ||
281 | # Check type of node y add data to host... | |
282 | if target_type == "maltego.DNSName": | |
283 | host.dns_name = self.getValue(target_node) | |
284 | elif target_type == "maltego.Website": | |
285 | host.website = self.getWebsite(target_node) | |
286 | elif target_type == "maltego.Netblock": | |
287 | host.netblock = self.getNetBlock(target_node) | |
288 | # Get location based in relation: netblock -> location | |
289 | host.location = self.getLocation(target_node) | |
290 | elif target_type == "maltego.MXRecord": | |
291 | host.mx_record = self.getValue(target_node) | |
292 | elif target_type == "maltego.NSRecord": | |
293 | host.ns_record = self.getValue(target_node) | |
294 | ||
295 | self.list_hosts.append(host) | |
296 | ||
297 | return self.list_hosts | |
298 | ||
299 | ||
300 | class MaltegoPlugin(PluginXMLFormat): | |
301 | ||
302 | def __init__(self): | |
303 | super().__init__() | |
304 | self.identifier_tag = "maltego" | |
305 | self.id = "Maltego" | |
306 | self.name = "Maltego MTGX Output Plugin" | |
307 | self.plugin_version = "1.0.1" | |
308 | self.version = "Maltego 3.6" | |
309 | self.framework_version = "1.0.0" | |
310 | self.current_path = None | |
311 | self.options = None | |
312 | self._current_output = None | |
313 | ||
314 | self._command_regex = re.compile( | |
315 | r'^(sudo maltego|maltego|\.\/maltego).*?') | |
316 | ||
317 | global current_path | |
318 | ||
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: | |
411 | 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 | |
438 | ||
439 | ||
440 | def createPlugin(): | |
441 | return MaltegoPlugin() | |
442 | ||
443 | ||
444 | if __name__ == "__main__": | |
445 | import sys | |
446 | import os | |
447 | if len(sys.argv) == 2: | |
448 | report_file = sys.argv[1] | |
449 | if os.path.isfile(report_file): | |
450 | plugin = createPlugin() | |
451 | plugin.processReport(report_file) | |
452 | print(plugin.get_json()) | |
453 | else: | |
454 | print(f"Report not found: {report_file}") | |
455 | else: | |
456 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
457 | # 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 | # 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 os | |
7 | import random | |
8 | import socket | |
9 | from faraday_plugins.plugins.plugin import PluginBase | |
10 | ||
11 | ||
12 | current_path = os.path.abspath(os.getcwd()) | |
13 | ||
14 | __author__ = "Francisco Amato" | |
15 | __copyright__ = "Copyright 2013, Faraday Project" | |
16 | __credits__ = ["Francisco Amato"] | |
17 | __license__ = "" | |
18 | __version__ = "1.0.0" | |
19 | __maintainer__ = "Francisco Amato" | |
20 | __email__ = "[email protected]" | |
21 | __status__ = "Development" | |
22 | ||
23 | ||
24 | class MedusaParser: | |
25 | """ | |
26 | The objective of this class is to parse an xml file generated by the medusa tool. | |
27 | ||
28 | @param medusa_filepath A proper simple report generated by medusa | |
29 | """ | |
30 | ||
31 | def __init__(self, xml_output): | |
32 | self.srv = { | |
33 | 'ftp': '21', 'http': '80', 'imap': '143', 'mssql': '1433', 'mysql': '3306', | |
34 | 'ncp': '524', 'nntp': '119', 'pcanywhere': '5631', 'pop3': '110', 'postgres': '5432', | |
35 | 'rexec': '512', 'rlogin': '513', 'rsh': '514', 'smbnt': 'smbnt', 'smtp': '25', | |
36 | 'smtp-vrfy': 'smtp-vrfy', 'snmp': '161', 'ssh': '22', 'svn': '3690', | |
37 | 'telnet': '23', 'vmauthd': 'vmauthd', 'vnc': '5900', 'web-form': 'web-form', | |
38 | 'wrapper': 'wrapper' | |
39 | } | |
40 | ||
41 | lines = xml_output.splitlines() | |
42 | self.items = [] | |
43 | ||
44 | for l in lines: | |
45 | ||
46 | reg = re.search( | |
47 | "ACCOUNT FOUND: \[([^$]+)\] Host: ([^$]+) User: ([^$]+) Password: ([^$]+) \[SUCCESS\]", | |
48 | l) | |
49 | ||
50 | print("REG" + str(reg)) | |
51 | ||
52 | if reg: | |
53 | ||
54 | item = { | |
55 | 'service': reg.group(1), | |
56 | 'host': reg.group(2), | |
57 | 'user': reg.group(3), | |
58 | 'pass': reg.group(4) | |
59 | } | |
60 | ||
61 | print("ITEM" + str(item)) | |
62 | item['ip'] = self.getAddress(item['host']) | |
63 | item['port'] = self.srv[item['service']] | |
64 | print("ITEM" + str(item)) | |
65 | 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 | ||
76 | ||
77 | class MedusaPlugin(PluginBase): | |
78 | """ | |
79 | Example plugin to parse medusa output. | |
80 | """ | |
81 | ||
82 | def __init__(self): | |
83 | super().__init__() | |
84 | self.id = "Medusa" | |
85 | self.name = "Medusa Output Plugin" | |
86 | self.plugin_version = "0.0.1" | |
87 | self.version = "2.1.1" | |
88 | 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 | ||
94 | self.host = None | |
95 | self.port = "" | |
96 | ||
97 | def parseOutputString(self, output, debug=False): | |
98 | """ | |
99 | This method will discard the output the shell sends, it will read it from | |
100 | the xml where it expects it to be present. | |
101 | ||
102 | NOTE: if 'debug' is true then it is being run from a test case and the | |
103 | output being sent is valid. | |
104 | """ | |
105 | parser = MedusaParser(output) | |
106 | ||
107 | for item in parser.items: | |
108 | ||
109 | h_id = self.createAndAddHost(item['ip']) | |
110 | if self._isIPV4(item['ip']): | |
111 | i_id = self.createAndAddInterface( | |
112 | h_id, | |
113 | item['ip'], | |
114 | ipv4_address=item['ip'], | |
115 | hostname_resolution=item['host']) | |
116 | else: | |
117 | i_id = self.createAndAddInterface( | |
118 | h_id, | |
119 | item['ip'], | |
120 | ipv6_address=item['ip'], | |
121 | hostname_resolution=item['host']) | |
122 | ||
123 | port = self.port if self.port else item['port'] | |
124 | ||
125 | s_id = self.createAndAddServiceToInterface( | |
126 | h_id, | |
127 | i_id, | |
128 | item['service'], | |
129 | ports=[port], | |
130 | protocol="tcp", | |
131 | status="open") | |
132 | ||
133 | self.createAndAddCredToService( | |
134 | h_id, | |
135 | s_id, | |
136 | item['user'], | |
137 | item['pass']) | |
138 | ||
139 | self.createAndAddVulnToService(h_id, | |
140 | s_id, | |
141 | "Weak Credentials", | |
142 | "[medusa found the following credentials]\nuser:%s\npass:%s" % (item['user'], item['pass']), | |
143 | severity="high") | |
144 | ||
145 | del parser | |
146 | ||
147 | xml_arg_re = re.compile(r"^.*(-O\s*[^\s]+).*$") | |
148 | ||
149 | def processCommandString(self, username, current_path, command_string): | |
150 | ||
151 | self.port = "" | |
152 | self._output_file_path = os.path.join( | |
153 | self.data_path, "medusa_output-%s.txt" % random.uniform(1, 10)) | |
154 | arg_match = self.xml_arg_re.match(command_string) | |
155 | ||
156 | mreg = re.search(r"\-n( |)([\d]+)", command_string) | |
157 | if mreg: | |
158 | self.port = mreg.group(2) | |
159 | ||
160 | if arg_match is None: | |
161 | return re.sub( | |
162 | r"(^.*?medusa?)", r"\1 -O %s" % self._output_file_path, | |
163 | command_string) | |
164 | else: | |
165 | return re.sub( | |
166 | arg_match.group(1), | |
167 | r"-O %s" % self._output_file_path, | |
168 | command_string) | |
169 | ||
170 | def _isIPV4(self, ip): | |
171 | if len(ip.split(".")) == 4: | |
172 | return True | |
173 | else: | |
174 | return False | |
175 | ||
176 | def setHost(self): | |
177 | pass | |
178 | ||
179 | ||
180 | def createPlugin(): | |
181 | return MedusaPlugin() | |
182 | ||
183 | ||
184 | if __name__ == "__main__": | |
185 | import sys | |
186 | import os | |
187 | if len(sys.argv) == 2: | |
188 | report_file = sys.argv[1] | |
189 | if os.path.isfile(report_file): | |
190 | plugin = createPlugin() | |
191 | plugin.processReport(report_file) | |
192 | print(plugin.get_json()) | |
193 | else: | |
194 | print(f"Report not found: {report_file}") | |
195 | else: | |
196 | print(f"USAGE {sys.argv[0]} REPORT_FILE") |
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 | if __name__ == "__main__": | |
120 | import sys | |
121 | import os | |
122 | if len(sys.argv) == 2: | |
123 | report_file = sys.argv[1] | |
124 | if os.path.isfile(report_file): | |
125 | plugin = createPlugin() | |
126 | plugin.processReport(report_file) | |
127 | print(plugin.get_json()) | |
128 | else: | |
129 | print(f"Report not found: {report_file}") | |
130 | else: | |
131 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
132 | ||
133 | ||
134 | # 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 | # 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 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
6 | import re | |
7 | import os | |
8 | import sys | |
9 | ||
10 | try: | |
11 | import xml.etree.cElementTree as ET | |
12 | import xml.etree.ElementTree as ET_ORIG | |
13 | ETREE_VERSION = ET_ORIG.VERSION | |
14 | except ImportError: | |
15 | import xml.etree.ElementTree as ET | |
16 | ETREE_VERSION = ET.VERSION | |
17 | ||
18 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] | |
19 | ||
20 | current_path = os.path.abspath(os.getcwd()) | |
21 | ||
22 | __author__ = "Francisco Amato" | |
23 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
24 | __credits__ = ["Francisco Amato"] | |
25 | __license__ = "" | |
26 | __version__ = "1.0.0" | |
27 | __maintainer__ = "Francisco Amato" | |
28 | __email__ = "[email protected]" | |
29 | __status__ = "Development" | |
30 | ||
31 | ||
32 | class MetasploitXmlParser: | |
33 | """ | |
34 | The objective of this class is to parse an xml file generated by the metasploit tool. | |
35 | ||
36 | TODO: Handle errors. | |
37 | TODO: Test metasploit output version. Handle what happens if the parser doesn't support it. | |
38 | TODO: Test cases. | |
39 | ||
40 | @param metasploit_xml_filepath A proper xml generated by metasploit | |
41 | """ | |
42 | ||
43 | def __init__(self, xml_output): | |
44 | tree = self.parse_xml(xml_output) | |
45 | if tree: | |
46 | servicesByWebsite = {} | |
47 | for site in tree.findall('web_sites/web_site'): | |
48 | servicesByWebsite[site.find('id').text] = site.find('service-id').text | |
49 | webVulnsByService = {} | |
50 | for v in [data for data in self.get_vulns(tree, servicesByWebsite)]: | |
51 | if v.service_id not in webVulnsByService: | |
52 | webVulnsByService[v.service_id] = [] | |
53 | webVulnsByService[v.service_id].append(v) | |
54 | ||
55 | self.hosts = [data for data in self.get_items( | |
56 | tree, | |
57 | webVulnsByService)] | |
58 | else: | |
59 | self.hosts = [] | |
60 | ||
61 | def parse_xml(self, xml_output): | |
62 | """ | |
63 | Open and parse an xml file. | |
64 | ||
65 | TODO: Write custom parser to just read the nodes that we need instead of | |
66 | reading the whole file. | |
67 | ||
68 | @return xml_tree An xml tree instance. None if error. | |
69 | """ | |
70 | try: | |
71 | tree = ET.fromstring(xml_output) | |
72 | except SyntaxError as err: | |
73 | print("SyntaxError: %s. %s" % (err, xml_output)) | |
74 | return None | |
75 | ||
76 | return tree | |
77 | ||
78 | def get_items(self, tree, webVulns): | |
79 | """ | |
80 | @return items A list of Host instances | |
81 | """ | |
82 | bugtype = "" | |
83 | ||
84 | for node in tree.findall('hosts/host'): | |
85 | yield Host(node, webVulns) | |
86 | ||
87 | def get_vulns(self, tree, services): | |
88 | """ | |
89 | @return items A list of WebVuln instances | |
90 | """ | |
91 | bugtype = "" | |
92 | for node in tree.findall('web_vulns/web_vuln'): | |
93 | yield WebVuln(node, services) | |
94 | ||
95 | ||
96 | def get_attrib_from_subnode(xml_node, subnode_xpath_expr, attrib_name): | |
97 | """ | |
98 | Finds a subnode in the item node and the retrieves a value from it | |
99 | ||
100 | @return An attribute value | |
101 | """ | |
102 | global ETREE_VERSION | |
103 | node = None | |
104 | ||
105 | if ETREE_VERSION[0] <= 1 and ETREE_VERSION[1] < 3: | |
106 | ||
107 | match_obj = re.search( | |
108 | "([^\@]+?)\[\@([^=]*?)=\'([^\']*?)\'", subnode_xpath_expr) | |
109 | if match_obj is not None: | |
110 | node_to_find = match_obj.group(1) | |
111 | xpath_attrib = match_obj.group(2) | |
112 | xpath_value = match_obj.group(3) | |
113 | for node_found in xml_node.findall(node_to_find): | |
114 | if node_found.attrib[xpath_attrib] == xpath_value: | |
115 | node = node_found | |
116 | break | |
117 | else: | |
118 | node = xml_node.find(subnode_xpath_expr) | |
119 | ||
120 | else: | |
121 | node = xml_node.find(subnode_xpath_expr) | |
122 | ||
123 | if node is not None: | |
124 | return node.get(attrib_name) | |
125 | ||
126 | return None | |
127 | ||
128 | ||
129 | class Host: | |
130 | ||
131 | def __init__(self, item_node, webVulnsByService): | |
132 | self.node = item_node | |
133 | self.id = self.get_text_from_subnode('id') | |
134 | self.host = self.get_text_from_subnode('name') | |
135 | self.ip = self.get_text_from_subnode('address') | |
136 | self.os = self.get_text_from_subnode('os-name') | |
137 | ||
138 | self.services = [] | |
139 | self.vulnsByService = {} | |
140 | self.vulnsByHost = [] | |
141 | self.notesByService = {} | |
142 | self.credsByService = {} | |
143 | for s in self.node.findall('services/service'): | |
144 | service = {'id': None, 'port': None, 'proto': None, | |
145 | 'state': None, 'name': None, 'info': None} | |
146 | for attr in service: | |
147 | service[attr] = s.find(attr).text | |
148 | if not service['name']: | |
149 | service['name'] = 'unknown' | |
150 | if not service['state']: | |
151 | service['state'] = 'unknown' | |
152 | if not service['info']: | |
153 | service['info'] = 'unknown' | |
154 | ||
155 | self.services.append(service) | |
156 | self.vulnsByService[service['id']] = [] | |
157 | self.notesByService[service['id']] = [] | |
158 | if service['id'] in webVulnsByService: | |
159 | self.vulnsByService[service['id']] += webVulnsByService[service['id']] | |
160 | ||
161 | for v in self.node.findall('vulns/vuln'): | |
162 | vuln = HostVuln(v) | |
163 | if vuln.service_id: | |
164 | self.vulnsByService[vuln.service_id].append(vuln) | |
165 | else: | |
166 | self.vulnsByHost.append(vuln) | |
167 | ||
168 | for n in self.node.findall('notes/note'): | |
169 | note = HostNote(n) | |
170 | key = self.id + "_" + note.service_id | |
171 | if key not in self.notesByService: | |
172 | self.notesByService[key] = [] | |
173 | ||
174 | self.notesByService[key].append(note) | |
175 | ||
176 | for c in self.node.findall('creds/cred'): | |
177 | cred = HostCred(c) | |
178 | key = cred.port | |
179 | if key not in self.credsByService: | |
180 | self.credsByService[key] = [] | |
181 | ||
182 | self.credsByService[key].append(cred) | |
183 | ||
184 | def get_text_from_subnode(self, subnode_xpath_expr): | |
185 | """ | |
186 | Finds a subnode in the host node and the retrieves a value from it. | |
187 | ||
188 | @return An attribute value | |
189 | """ | |
190 | sub_node = self.node.find(subnode_xpath_expr) | |
191 | if sub_node is not None: | |
192 | if sub_node.text is not None: | |
193 | return sub_node.text | |
194 | ||
195 | return None | |
196 | ||
197 | ||
198 | class WebVuln: | |
199 | ||
200 | def __init__(self, item_node, services): | |
201 | self.node = item_node | |
202 | self.name = self.get_text_from_subnode('name') | |
203 | self.desc = self.get_text_from_subnode('description') | |
204 | self.host = self.get_text_from_subnode('vhost') | |
205 | self.port = self.get_text_from_subnode('port') | |
206 | self.ip = self.get_text_from_subnode('host') | |
207 | self.path = self.get_text_from_subnode('path') | |
208 | self.method = self.get_text_from_subnode('method') | |
209 | self.params = self.get_text_from_subnode('params') | |
210 | self.pname = self.get_text_from_subnode('pname') | |
211 | self.risk = self.get_text_from_subnode('risk') | |
212 | self.confidence = self.get_text_from_subnode('confidence') | |
213 | self.query = self.get_text_from_subnode('query') | |
214 | self.request = self.get_text_from_subnode('request') | |
215 | self.category = self.get_text_from_subnode('category-id') | |
216 | self.service_id = services[self.get_text_from_subnode('web-site-id')] | |
217 | self.isWeb = True | |
218 | ||
219 | def get_text_from_subnode(self, subnode_xpath_expr): | |
220 | """ | |
221 | Finds a subnode in the host node and the retrieves a value from it. | |
222 | ||
223 | @return An attribute value | |
224 | """ | |
225 | sub_node = self.node.find(subnode_xpath_expr) | |
226 | if sub_node is not None: | |
227 | if sub_node.text is not None: | |
228 | return sub_node.text | |
229 | ||
230 | return "" | |
231 | ||
232 | ||
233 | class HostNote: | |
234 | """ | |
235 | An abstract representation of a HostNote | |
236 | ||
237 | ||
238 | @param item_node A item_node taken from an metasploit xml tree | |
239 | """ | |
240 | ||
241 | def __init__(self, item_node): | |
242 | self.node = item_node | |
243 | self.service_id = self.get_text_from_subnode( | |
244 | 'service-id') if not None else "" | |
245 | self.host_id = self.get_text_from_subnode('host-id') | |
246 | self.ntype = self.get_text_from_subnode('ntype') | |
247 | self.data = self.get_text_from_subnode('data') | |
248 | ||
249 | def get_text_from_subnode(self, subnode_xpath_expr): | |
250 | """ | |
251 | Finds a subnode in the host node and the retrieves a value from it. | |
252 | ||
253 | @return An attribute value | |
254 | """ | |
255 | sub_node = self.node.find(subnode_xpath_expr) | |
256 | if sub_node is not None: | |
257 | if sub_node.text is not None: | |
258 | return sub_node.text | |
259 | ||
260 | return "" | |
261 | ||
262 | ||
263 | class HostCred: | |
264 | """ | |
265 | An abstract representation of a HostNote | |
266 | ||
267 | ||
268 | @param item_node A item_node taken from an metasploit xml tree | |
269 | """ | |
270 | ||
271 | def __init__(self, item_node): | |
272 | self.node = item_node | |
273 | self.port = self.get_text_from_subnode('port') | |
274 | self.user = self.get_text_from_subnode('user') | |
275 | self.passwd = self.get_text_from_subnode('pass') | |
276 | self.ptype = self.get_text_from_subnode('ptype') | |
277 | self.sname = self.get_text_from_subnode('sname') | |
278 | ||
279 | def get_text_from_subnode(self, subnode_xpath_expr): | |
280 | """ | |
281 | Finds a subnode in the host node and the retrieves a value from it. | |
282 | ||
283 | @return An attribute value | |
284 | """ | |
285 | sub_node = self.node.find(subnode_xpath_expr) | |
286 | if sub_node is not None: | |
287 | if sub_node.text is not None: | |
288 | return sub_node.text | |
289 | ||
290 | return "" | |
291 | ||
292 | ||
293 | class HostVuln: | |
294 | """ | |
295 | An abstract representation of a HostVuln | |
296 | ||
297 | ||
298 | @param item_node A item_node taken from an metasploit xml tree | |
299 | """ | |
300 | ||
301 | def __init__(self, item_node): | |
302 | self.node = item_node | |
303 | self.service_id = self.get_text_from_subnode('service-id') | |
304 | self.name = self.get_text_from_subnode('name') | |
305 | self.desc = self.get_text_from_subnode('info') | |
306 | self.refs = [r.text for r in self.node.findall('refs/ref')] | |
307 | self.exploited_date = self.get_text_from_subnode('exploited-at') | |
308 | self.exploited = (self.exploited_date is not None) | |
309 | self.isWeb = False | |
310 | ||
311 | def get_text_from_subnode(self, subnode_xpath_expr): | |
312 | """ | |
313 | Finds a subnode in the host node and the retrieves a value from it. | |
314 | ||
315 | @return An attribute value | |
316 | """ | |
317 | sub_node = self.node.find(subnode_xpath_expr) | |
318 | if sub_node is not None: | |
319 | if sub_node.text is not None: | |
320 | return sub_node.text | |
321 | ||
322 | return "" | |
323 | ||
324 | ||
325 | class MetasploitPlugin(PluginXMLFormat): | |
326 | """ | |
327 | Example plugin to parse metasploit output. | |
328 | """ | |
329 | ||
330 | def __init__(self): | |
331 | super().__init__() | |
332 | self.identifier_tag = ["MetasploitV4", "MetasploitV5"] | |
333 | self.id = "Metasploit" | |
334 | self.name = "Metasploit XML Output Plugin" | |
335 | self.plugin_version = "0.0.1" | |
336 | self.version = "4.7.2" | |
337 | self.framework_version = "1.0.0" | |
338 | self.options = None | |
339 | self._current_output = None | |
340 | self.target = None | |
341 | self._command_regex = re.compile(r'^(metasploit|sudo metasploit|\.\/metasploit).*?') | |
342 | ||
343 | ||
344 | def parseOutputString(self, output, debug=False): | |
345 | """ | |
346 | This method will discard the output the shell sends, it will read it from | |
347 | the xml where it expects it to be present. | |
348 | ||
349 | NOTE: if 'debug' is true then it is being run from a test case and the | |
350 | output being sent is valid. | |
351 | """ | |
352 | ||
353 | parser = MetasploitXmlParser(output) | |
354 | ||
355 | for item in parser.hosts: | |
356 | self.hostnames = [] | |
357 | if item.host: | |
358 | self.hostnames = [item.host] | |
359 | ||
360 | h_id = self.createAndAddHost(item.ip, os=item.os, hostnames=self.hostnames) | |
361 | ||
362 | if item.id + "_" in item.notesByService: | |
363 | for n in item.notesByService[item.id + "_"]: | |
364 | self.createAndAddNoteToHost(h_id, n.ntype, n.data) | |
365 | ||
366 | for v in item.vulnsByHost: | |
367 | v_id = self.createAndAddVulnToHost( | |
368 | h_id, v.name, v.desc, ref=v.refs) | |
369 | ||
370 | for s in item.services: | |
371 | s_id = self.createAndAddServiceToHost(h_id, s['name'], | |
372 | protocol=s['proto'], | |
373 | ports=[s['port']], | |
374 | status=s['state'], | |
375 | description=s['info']) | |
376 | ||
377 | if item.id + "_" + s['id'] in item.notesByService: | |
378 | for n in item.notesByService[item.id + "_" + s['id']]: | |
379 | self.createAndAddNoteToService( | |
380 | h_id, s_id, n.ntype, n.data) | |
381 | ||
382 | if s['port'] in item.credsByService: | |
383 | for c in item.credsByService[s['port']]: | |
384 | self.createAndAddCredToService( | |
385 | h_id, s_id, c.user, c.passwd) | |
386 | self.createAndAddVulnToService(h_id, s_id, "Weak Credentials", "[metasploit found the following credentials]\nuser:%s\npass:%s" % ( | |
387 | c.user, c.passwd), severity="high") | |
388 | ||
389 | for v in item.vulnsByService[s['id']]: | |
390 | if v.isWeb: | |
391 | v_id = self.createAndAddVulnWebToService(h_id, s_id, v.name, v.desc, | |
392 | severity=v.risk, website=v.host, | |
393 | path=v.path, request=v.request, method=v.method, | |
394 | pname=v.pname, params=v.params, query=v.query, | |
395 | category=v.category) | |
396 | else: | |
397 | v_id = self.createAndAddVulnToService( | |
398 | h_id, s_id, v.name, v.desc, ref=v.refs) | |
399 | ||
400 | del parser | |
401 | ||
402 | def _isIPV4(self, ip): | |
403 | if len(ip.split(".")) == 4: | |
404 | return True | |
405 | else: | |
406 | return False | |
407 | ||
408 | def processCommandString(self, username, current_path, command_string): | |
409 | return None | |
410 | ||
411 | def setHost(self): | |
412 | pass | |
413 | ||
414 | ||
415 | def createPlugin(): | |
416 | return MetasploitPlugin() | |
417 | ||
418 | ||
419 | if __name__ == "__main__": | |
420 | import sys | |
421 | import os | |
422 | if len(sys.argv) == 2: | |
423 | report_file = sys.argv[1] | |
424 | if os.path.isfile(report_file): | |
425 | plugin = createPlugin() | |
426 | plugin.processReport(report_file) | |
427 | print(plugin.get_json()) | |
428 | else: | |
429 | print(f"Report not found: {report_file}") | |
430 | else: | |
431 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
432 | # 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 | """ | |
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 | """ | |
6 | from faraday_plugins.plugins.plugin import PluginBase | |
7 | import re | |
8 | ||
9 | try: | |
10 | import xml.etree.cElementTree as ET | |
11 | import xml.etree.ElementTree as ET_ORIG | |
12 | except ImportError: | |
13 | import xml.etree.ElementTree as ET | |
14 | ||
15 | __author__ = 'Ezequiel Tavella' | |
16 | __copyright__ = 'Copyright (c) 2016, Infobyte LLC' | |
17 | __credits__ = ['Ezequiel Tavella'] | |
18 | __license__ = '' | |
19 | __version__ = '1.0.0' | |
20 | __email__ = "[email protected]" | |
21 | __status__ = "Development" | |
22 | ||
23 | ||
24 | class NdiffXmlParser(): | |
25 | """ | |
26 | The objective of this class is to parse an xml file generated by | |
27 | the ndiff tool. | |
28 | """ | |
29 | ||
30 | def __init__(self, xmlOutput): | |
31 | self.tree = self.parse_xml(xmlOutput) | |
32 | ||
33 | if self.tree: | |
34 | self.hostDiff = self.getHostsDiffs(self.tree) | |
35 | else: | |
36 | self.hostDiff = [] | |
37 | ||
38 | def parse_xml(self, xmlOutput): | |
39 | ||
40 | # Open and parse an xml output | |
41 | ||
42 | try: | |
43 | return ET.fromstring(xmlOutput) | |
44 | except SyntaxError as err: | |
45 | print("SyntaxError: %s" % err) | |
46 | return None | |
47 | ||
48 | def getHostsDiffs(self, tree): | |
49 | """ | |
50 | @return hosts A list of HostDiff instances | |
51 | """ | |
52 | for node in tree.findall('scandiff/hostdiff'): | |
53 | yield HostDiff(node) | |
54 | ||
55 | ||
56 | class HostDiff(): | |
57 | ||
58 | # Abstraction of a Hosts Diff | |
59 | # Search for a new host in the second scan and new ports opened or changed | |
60 | # of status... | |
61 | def __init__(self, hostDiff): | |
62 | ||
63 | self.isNewHost = False | |
64 | self.hostXml = self.getHostXml(hostDiff) | |
65 | ||
66 | self.ip = self.getIp() | |
67 | self.ports = self.getPorts() | |
68 | ||
69 | def getHostXml(self, hostDiff): | |
70 | ||
71 | host = hostDiff.find('host') | |
72 | if host is not None: | |
73 | return host | |
74 | else: | |
75 | self.isNewHost = True | |
76 | return hostDiff.find('b/host') | |
77 | ||
78 | def getIp(self): | |
79 | if self.hostXml is None: | |
80 | return None | |
81 | return self.hostXml.find('address').get('addr') | |
82 | ||
83 | def getPorts(self): | |
84 | ||
85 | ports = [] | |
86 | if self.hostXml is None: | |
87 | return ports | |
88 | ||
89 | if self.isNewHost: | |
90 | ||
91 | for port in self.hostXml.find('ports').findall('port'): | |
92 | ports.append( | |
93 | [port.get('portid'), port.find('state').get('state')]) | |
94 | return ports | |
95 | ||
96 | else: | |
97 | ||
98 | for port in self.hostXml.find('ports').findall('portdiff'): | |
99 | if port.find('b/port'): | |
100 | ports.append([port.find('b/port').get('portid'), | |
101 | port.find('b/port/state').get('state')]) | |
102 | return ports | |
103 | ||
104 | ||
105 | class CmdNdiffPlugin(PluginBase): | |
106 | """ | |
107 | This plugin handles ndiff command. | |
108 | Add a new vuln INFO if detect a new host or a new port .. | |
109 | """ | |
110 | ||
111 | def __init__(self): | |
112 | super().__init__() | |
113 | self.id = "Ndiff" | |
114 | self.name = "ndiff" | |
115 | self.plugin_version = "0.0.1" | |
116 | self.version = "1.0.0" | |
117 | self._command_regex = re.compile(r'^(sudo ndiff|ndiff).*?') | |
118 | ||
119 | def parseOutputString(self, output, debug=False): | |
120 | ||
121 | parser = NdiffXmlParser(output) | |
122 | ||
123 | for host in parser.hostDiff: | |
124 | ||
125 | if host.ip is None: | |
126 | continue | |
127 | ||
128 | if host.isNewHost: | |
129 | hostId = self.createAndAddHost(host.ip, '') | |
130 | ||
131 | description = '%s is a NEW host active.\n' % host.ip | |
132 | for port in host.ports: | |
133 | description += 'Port: %s/%s\n' % (port[0], port[1]) | |
134 | ||
135 | self.createAndAddVulnToHost( | |
136 | hostId, | |
137 | 'New host active', | |
138 | description, | |
139 | ['Ndiff tool'], | |
140 | 'INFO' | |
141 | ) | |
142 | else: | |
143 | ||
144 | if host.ports == []: | |
145 | continue | |
146 | ||
147 | hostId = self.createAndAddHost(host.ip, '') | |
148 | ||
149 | description = 'New service/s found.\n' | |
150 | for port in host.ports: | |
151 | description += 'Port: %s/%s\n' % (port[0], port[1]) | |
152 | ||
153 | self.createAndAddVulnToHost( | |
154 | hostId, | |
155 | 'New ports actives', | |
156 | description, | |
157 | ['Ndiff tool'], | |
158 | 'INFO' | |
159 | ) | |
160 | ||
161 | def processCommandString(self, username, current_path, command_string): | |
162 | if command_string.find('--xml') < 0: | |
163 | return command_string + '--xml' | |
164 | ||
165 | ||
166 | def createPlugin(): | |
167 | return CmdNdiffPlugin() | |
168 | ||
169 | if __name__ == "__main__": | |
170 | import sys | |
171 | import os | |
172 | if len(sys.argv) == 2: | |
173 | report_file = sys.argv[1] | |
174 | if os.path.isfile(report_file): | |
175 | plugin = createPlugin() | |
176 | plugin.processReport(report_file) | |
177 | print(plugin.get_json()) | |
178 | else: | |
179 | print(f"Report not found: {report_file}") | |
180 | else: | |
181 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
182 | # 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 | # I'm Py3⏎ |
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⏎ |
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 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"] | |
21 | __license__ = "" | |
22 | __version__ = "1.0.0" | |
23 | __maintainer__ = "Francisco Amato" | |
24 | __email__ = "[email protected]" | |
25 | __status__ = "Development" | |
26 | ||
27 | ||
28 | class NessusParser: | |
29 | """ | |
30 | The objective of this class is to parse an xml file generated by the nessus tool. | |
31 | ||
32 | TODO: Handle errors. | |
33 | TODO: Test nessus output version. Handle what happens if the parser doesn't support it. | |
34 | TODO: Test cases. | |
35 | ||
36 | @param nessus_filepath A proper simple report generated by nessus | |
37 | """ | |
38 | ||
39 | 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 | |
52 | ||
53 | ||
54 | class NessusPlugin(PluginXMLFormat): | |
55 | """ | |
56 | Example plugin to parse nessus output. | |
57 | """ | |
58 | ||
59 | def __init__(self): | |
60 | super().__init__() | |
61 | self.extension = ".nessus" | |
62 | self.identifier_tag = "NessusClientData_v2" | |
63 | self.id = "Nessus" | |
64 | self.name = "Nessus XML Output Plugin" | |
65 | self.plugin_version = "0.0.1" | |
66 | self.version = "5.2.4" | |
67 | self.framework_version = "1.0.1" | |
68 | 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): | |
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 | p = dotnessus_v2.Report() | |
94 | 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) | |
157 | 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) | |
174 | 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 | |
182 | 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 | |
197 | ||
198 | ||
199 | def createPlugin(): | |
200 | return NessusPlugin() | |
201 | ||
202 | if __name__ == "__main__": | |
203 | import sys | |
204 | import os | |
205 | if len(sys.argv) == 2: | |
206 | report_file = sys.argv[1] | |
207 | if os.path.isfile(report_file): | |
208 | plugin = createPlugin() | |
209 | plugin.processReport(report_file) | |
210 | print(plugin.get_json()) | |
211 | else: | |
212 | print(f"Report not found: {report_file}") | |
213 | else: | |
214 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
215 | # 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 |
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 | # 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 | from faraday_plugins.plugins.plugin import PluginBase | |
6 | import re | |
7 | ||
8 | __author__ = "Federico Fernandez - @q3rv0" | |
9 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
10 | __license__ = "" | |
11 | __version__ = "1.0.0" | |
12 | __maintainer__ = "Federico Fernandez" | |
13 | __email__ = "[email protected]" | |
14 | __status__ = "Development" | |
15 | ||
16 | class NetdiscoverPlugin(PluginBase): | |
17 | ||
18 | def __init__(self): | |
19 | super().__init__() | |
20 | self.id = "Netdiscover" | |
21 | self.name = "netdiscover" | |
22 | self.plugin_version = "0.0.1" | |
23 | self.version = "1.0.0" | |
24 | self._command_regex = re.compile(r'^(sudo netdiscover|netdiscover).*?') | |
25 | ||
26 | def parseOutputString(self, output, debug=False): | |
27 | #regexp get ip, mac and hostname | |
28 | reg = re.findall(r"(([0-9]+\.?){4})\s+(([0-9a-f]+\:?){6})((\s+[0-9]+){2})(.*)", output) | |
29 | ||
30 | if output.find('Finished!') != -1 and len(reg) > 0: | |
31 | ||
32 | for stdout in reg: | |
33 | ip_address = stdout[0] | |
34 | mac = stdout[2] | |
35 | hostname = stdout[6].strip() | |
36 | ||
37 | h_id = self.createAndAddHost(ip_address) | |
38 | self.createAndAddInterface(h_id, ip_address, ipv4_address=ip_address, mac=mac, hostname_resolution=[hostname]) | |
39 | ||
40 | return True | |
41 | ||
42 | def processCommandString(self, username, current_path, command_string): | |
43 | return None | |
44 | ||
45 | ||
46 | def createPlugin(): | |
47 | return NetdiscoverPlugin() | |
48 | ||
49 | if __name__ == "__main__": | |
50 | import sys | |
51 | import os | |
52 | if len(sys.argv) == 2: | |
53 | report_file = sys.argv[1] | |
54 | if os.path.isfile(report_file): | |
55 | plugin = createPlugin() | |
56 | plugin.processReport(report_file) | |
57 | print(plugin.get_json()) | |
58 | else: | |
59 | print(f"Report not found: {report_file}") | |
60 | else: | |
61 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
62 | ||
63 | # 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 | # 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 PluginXMLFormat | |
7 | import re | |
8 | import os | |
9 | import sys | |
10 | import socket | |
11 | import urllib | |
12 | from bs4 import BeautifulSoup | |
13 | ||
14 | try: | |
15 | import xml.etree.cElementTree as ET | |
16 | import xml.etree.ElementTree as ET_ORIG | |
17 | ETREE_VERSION = ET_ORIG.VERSION | |
18 | except ImportError: | |
19 | import xml.etree.ElementTree as ET | |
20 | ETREE_VERSION = ET.VERSION | |
21 | ||
22 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] | |
23 | ||
24 | current_path = os.path.abspath(os.getcwd()) | |
25 | ||
26 | __author__ = "Francisco Amato" | |
27 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
28 | __credits__ = ["Francisco Amato"] | |
29 | __license__ = "" | |
30 | __version__ = "1.0.0" | |
31 | __maintainer__ = "Francisco Amato" | |
32 | __email__ = "[email protected]" | |
33 | __status__ = "Development" | |
34 | ||
35 | ||
36 | class NetsparkerXmlParser: | |
37 | """ | |
38 | The objective of this class is to parse an xml file generated by the netsparker tool. | |
39 | ||
40 | TODO: Handle errors. | |
41 | TODO: Test netsparker output version. Handle what happens if the parser doesn't support it. | |
42 | TODO: Test cases. | |
43 | ||
44 | @param netsparker_xml_filepath A proper xml generated by netsparker | |
45 | """ | |
46 | ||
47 | def __init__(self, xml_output): | |
48 | self.filepath = xml_output | |
49 | ||
50 | tree = self.parse_xml(xml_output) | |
51 | if tree: | |
52 | self.items = [data for data in self.get_items(tree)] | |
53 | else: | |
54 | self.items = [] | |
55 | ||
56 | def parse_xml(self, xml_output): | |
57 | """ | |
58 | Open and parse an xml file. | |
59 | ||
60 | TODO: Write custom parser to just read the nodes that we need instead of | |
61 | reading the whole file. | |
62 | ||
63 | @return xml_tree An xml tree instance. None if error. | |
64 | """ | |
65 | try: | |
66 | tree = ET.fromstring(xml_output) | |
67 | except SyntaxError as err: | |
68 | self.devlog("SyntaxError: %s. %s" % (err, xml_output)) | |
69 | return None | |
70 | ||
71 | return tree | |
72 | ||
73 | def get_items(self, tree): | |
74 | """ | |
75 | @return items A list of Host instances | |
76 | """ | |
77 | for node in tree.findall("vulnerability"): | |
78 | yield Item(node) | |
79 | ||
80 | ||
81 | class Item: | |
82 | """ | |
83 | An abstract representation of a Item | |
84 | ||
85 | ||
86 | @param item_node A item_node taken from an netsparker xml tree | |
87 | """ | |
88 | ||
89 | def re_map_severity(self, severity): | |
90 | if severity == "Important": | |
91 | return "high" | |
92 | return severity | |
93 | ||
94 | def __init__(self, item_node, encoding="ascii"): | |
95 | self.node = item_node | |
96 | self.url = self.get_text_from_subnode("url") | |
97 | ||
98 | host = re.search( | |
99 | "(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) | |
100 | ||
101 | self.protocol = host.group(1) | |
102 | self.hostname = host.group(4) | |
103 | self.port = 80 | |
104 | ||
105 | if self.protocol == 'https': | |
106 | self.port = 443 | |
107 | if host.group(11) is not None: | |
108 | self.port = host.group(11) | |
109 | ||
110 | self.name = self.get_text_from_subnode("type") | |
111 | self.desc = self.get_text_from_subnode("description") | |
112 | self.severity = self.re_map_severity(self.get_text_from_subnode("severity")) | |
113 | self.certainty = self.get_text_from_subnode("certainty") | |
114 | self.method = self.get_text_from_subnode("vulnerableparametertype") | |
115 | self.param = self.get_text_from_subnode("vulnerableparameter") | |
116 | self.paramval = self.get_text_from_subnode("vulnerableparametervalue") | |
117 | self.reference = self.get_text_from_subnode("externalReferences") | |
118 | self.resolution = self.get_text_from_subnode("actionsToTake") | |
119 | self.request = self.get_text_from_subnode("rawrequest") | |
120 | self.response = self.get_text_from_subnode("rawresponse") | |
121 | if self.response: | |
122 | self.response = self.response.encode(encoding,errors="backslashreplace").decode(encoding) | |
123 | if self.request: | |
124 | self.request = self.request.encode(encoding,errors="backslashreplace").decode(encoding) | |
125 | if self.reference: | |
126 | self.reference = self.reference.encode(encoding,errors="backslashreplace").decode(encoding) | |
127 | ||
128 | ||
129 | self.kvulns = [] | |
130 | for v in self.node.findall("knownvulnerabilities/knownvulnerability"): | |
131 | self.node = v | |
132 | self.kvulns.append(self.get_text_from_subnode( | |
133 | "severity") + "-" + self.get_text_from_subnode("title")) | |
134 | ||
135 | self.extra = [] | |
136 | for v in item_node.findall("extrainformation/info"): | |
137 | self.extra.append(v.get('name') + ":" + v.text) | |
138 | ||
139 | self.node = item_node | |
140 | self.node = item_node.find("classification") | |
141 | self.owasp = self.get_text_from_subnode("OWASP") | |
142 | self.wasc = self.get_text_from_subnode("WASC") | |
143 | self.cwe = self.get_text_from_subnode("CWE") | |
144 | self.capec = self.get_text_from_subnode("CAPEC") | |
145 | self.pci = self.get_text_from_subnode("PCI") | |
146 | self.pci2 = self.get_text_from_subnode("PCI2") | |
147 | self.node = item_node.find("classification/CVSS") | |
148 | self.cvss = self.get_text_from_subnode("vector") | |
149 | ||
150 | self.ref = [] | |
151 | if self.cwe: | |
152 | self.ref.append("CWE-" + self.cwe) | |
153 | if self.owasp: | |
154 | self.ref.append("OWASP-" + self.owasp) | |
155 | if self.reference: | |
156 | self.ref.extend(list(set(re.findall('https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+', self.reference)))) | |
157 | if self.cvss: | |
158 | self.ref.append(self.cvss) | |
159 | ||
160 | self.data = "" | |
161 | self.data += "\nKnowVulns: " + \ | |
162 | "\n".join(self.kvulns) if self.kvulns else "" | |
163 | self.data += "\nWASC: " + self.wasc if self.wasc else "" | |
164 | self.data += "\nCertainty: " + self.certainty if self.certainty else "" | |
165 | self.data += "\nPCI: " + self.pci if self.pci else "" | |
166 | self.data += "\nPCI2: " + self.pci2 if self.pci2 else "" | |
167 | self.data += "\nCAPEC: " + self.capec if self.capec else "" | |
168 | self.data += "\nPARAM: " + self.param if self.param else "" | |
169 | self.data += "\nPARAM VAL: " + \ | |
170 | repr(self.paramval) if self.paramval else "" | |
171 | self.data += "\nExtra: " + "\n".join(self.extra) if self.extra else "" | |
172 | ||
173 | def get_text_from_subnode(self, subnode_xpath_expr): | |
174 | """ | |
175 | Finds a subnode in the host node and the retrieves a value from it. | |
176 | ||
177 | @return An attribute value | |
178 | """ | |
179 | if self.node: | |
180 | sub_node = self.node.find(subnode_xpath_expr) | |
181 | if sub_node is not None: | |
182 | return sub_node.text | |
183 | ||
184 | return None | |
185 | ||
186 | ||
187 | class NetsparkerPlugin(PluginXMLFormat): | |
188 | """ | |
189 | Example plugin to parse netsparker output. | |
190 | """ | |
191 | ||
192 | def __init__(self): | |
193 | super().__init__() | |
194 | self.identifier_tag = "netsparker" | |
195 | self.id = "Netsparker" | |
196 | self.name = "Netsparker XML Output Plugin" | |
197 | self.plugin_version = "0.0.1" | |
198 | self.version = "Netsparker 3.1.1.0" | |
199 | self.framework_version = "1.0.0" | |
200 | self.options = None | |
201 | self._current_output = None | |
202 | self._command_regex = re.compile( | |
203 | r'^(sudo netsparker|\.\/netsparker).*?') | |
204 | ||
205 | ||
206 | def resolve(self, host): | |
207 | try: | |
208 | return socket.gethostbyname(host) | |
209 | except: | |
210 | pass | |
211 | return host | |
212 | ||
213 | def parseOutputString(self, output, debug=False): | |
214 | ||
215 | parser = NetsparkerXmlParser(output) | |
216 | first = True | |
217 | for i in parser.items: | |
218 | if first: | |
219 | ip = self.resolve(i.hostname) | |
220 | h_id = self.createAndAddHost(ip, hostnames=[ip]) | |
221 | ||
222 | s_id = self.createAndAddServiceToHost(h_id, str(i.port), | |
223 | protocol = str(i.protocol), | |
224 | ports=[str(i.port)], | |
225 | status="open") | |
226 | first = False | |
227 | ||
228 | v_id = self.createAndAddVulnWebToService(h_id, s_id, i.name, ref=i.ref, website=i.hostname, | |
229 | severity=i.severity, desc=BeautifulSoup(i.desc, "lxml").text, | |
230 | path=i.url, method=i.method, request=i.request, response=i.response, | |
231 | resolution=BeautifulSoup(i.resolution, "lxml").text,pname=i.param, data=i.data) | |
232 | ||
233 | del parser | |
234 | ||
235 | def processCommandString(self, username, current_path, command_string): | |
236 | return None | |
237 | ||
238 | ||
239 | def createPlugin(): | |
240 | return NetsparkerPlugin() | |
241 | ||
242 | if __name__ == "__main__": | |
243 | import sys | |
244 | import os | |
245 | if len(sys.argv) == 2: | |
246 | report_file = sys.argv[1] | |
247 | if os.path.isfile(report_file): | |
248 | plugin = createPlugin() | |
249 | plugin.processReport(report_file) | |
250 | print(plugin.get_json()) | |
251 | else: | |
252 | print(f"Report not found: {report_file}") | |
253 | else: | |
254 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
255 | ||
256 | # 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 | # 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 PluginXMLFormat | |
7 | import re | |
8 | import os | |
9 | import socket | |
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 | |
18 | ||
19 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] | |
20 | ||
21 | current_path = os.path.abspath(os.getcwd()) | |
22 | ||
23 | __author__ = "Francisco Amato" | |
24 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
25 | __credits__ = ["Francisco Amato"] | |
26 | __license__ = "" | |
27 | __version__ = "1.0.0" | |
28 | __maintainer__ = "Francisco Amato" | |
29 | __email__ = "[email protected]" | |
30 | __status__ = "Development" | |
31 | ||
32 | ||
33 | def cleaner_unicode(string): | |
34 | if string is not None: | |
35 | return string.encode('ascii', errors='backslashreplace') | |
36 | 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) | |
50 | return urls | |
51 | ||
52 | ||
53 | class NetsparkerCloudXmlParser: | |
54 | """ | |
55 | The objective of this class is to parse an xml file generated by the netsparkercloud tool. | |
56 | ||
57 | TODO: Handle errors. | |
58 | TODO: Test netsparkercloud output version. Handle what happens if the parser doesn't support it. | |
59 | TODO: Test cases. | |
60 | ||
61 | @param netsparkercloud_xml_filepath A proper xml generated by netsparkercloud | |
62 | """ | |
63 | ||
64 | def __init__(self, xml_output): | |
65 | self.filepath = xml_output | |
66 | ||
67 | tree = self.parse_xml(xml_output) | |
68 | if tree: | |
69 | self.items = [data for data in self.get_items(tree)] | |
70 | else: | |
71 | self.items = [] | |
72 | ||
73 | def parse_xml(self, xml_output): | |
74 | """ | |
75 | Open and parse an xml file. | |
76 | ||
77 | TODO: Write custom parser to just read the nodes that we need instead of | |
78 | reading the whole file. | |
79 | ||
80 | @return xml_tree An xml tree instance. None if error. | |
81 | """ | |
82 | try: | |
83 | tree = ET.fromstring(xml_output) | |
84 | except SyntaxError as err: | |
85 | self.devlog("SyntaxError: %s. %s" % (err, xml_output)) | |
86 | return None | |
87 | ||
88 | return tree | |
89 | ||
90 | def get_items(self, tree): | |
91 | """ | |
92 | @return items A list of Host instances | |
93 | """ | |
94 | for node in tree.findall("vulnerabilities/vulnerability"): | |
95 | yield Item(node) | |
96 | ||
97 | ||
98 | class Item: | |
99 | """ | |
100 | An abstract representation of a Item | |
101 | ||
102 | ||
103 | @param item_node A item_node taken from an netsparkercloud xml tree | |
104 | """ | |
105 | def re_map_severity(self, severity): | |
106 | if severity == "Important": | |
107 | return "high" | |
108 | return severity | |
109 | ||
110 | def __init__(self, item_node): | |
111 | 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 | ||
126 | self.type = self.get_text_from_subnode("type") | |
127 | self.name = self.get_text_from_subnode("name") | |
128 | self.severity = self.re_map_severity(self.get_text_from_subnode("severity")) | |
129 | self.certainty = self.get_text_from_subnode("certainty") | |
130 | ||
131 | ||
132 | self.node = item_node.find("http-request") | |
133 | self.method = self.get_text_from_subnode("method") | |
134 | self.request = self.get_text_from_subnode("content") | |
135 | ||
136 | #print self.node | |
137 | self.param = "" | |
138 | self.paramval = "" | |
139 | for p in self.node.findall("parameters/parameter"): | |
140 | self.param = p.get('name') | |
141 | self.paramval = p.get('value') | |
142 | ||
143 | self.node = item_node.find("http-response") | |
144 | self.response = self.get_text_from_subnode("content") | |
145 | ||
146 | self.extra = [] | |
147 | for v in item_node.findall("extra-information/info"): | |
148 | self.extra.append(v.get('name') + ":" + v.get('value') ) | |
149 | ||
150 | self.node = item_node.find("classification") | |
151 | self.owasp = self.get_text_from_subnode("owasp") | |
152 | self.wasc = self.get_text_from_subnode("wasc") | |
153 | self.cwe = self.get_text_from_subnode("cwe") | |
154 | self.capec = self.get_text_from_subnode("capec") | |
155 | self.pci = self.get_text_from_subnode("pci31") | |
156 | self.pci2 = self.get_text_from_subnode("pci32") | |
157 | self.hipaa = self.get_text_from_subnode("hipaa") | |
158 | ||
159 | self.ref = [] | |
160 | if self.cwe: | |
161 | self.ref.append("CWE-" + self.cwe) | |
162 | if self.owasp: | |
163 | self.ref.append("OWASP-" + self.owasp) | |
164 | ||
165 | self.node = item_node | |
166 | self.remedyreferences = self.get_text_from_subnode("remedy-references") | |
167 | self.externalreferences = self.get_text_from_subnode("external-references") | |
168 | if self.remedyreferences: | |
169 | for u in get_urls(self.remedyreferences): | |
170 | self.ref.append(u) | |
171 | if self.externalreferences: | |
172 | for u in get_urls(self.externalreferences): | |
173 | self.ref.append(u) | |
174 | ||
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 "" | |
195 | ||
196 | def get_text_from_subnode(self, subnode_xpath_expr): | |
197 | """ | |
198 | Finds a subnode in the host node and the retrieves a value from it. | |
199 | ||
200 | @return An attribute value | |
201 | """ | |
202 | if self.node: | |
203 | sub_node = self.node.find(subnode_xpath_expr) | |
204 | if sub_node is not None: | |
205 | if sub_node.text is not None: | |
206 | return cleaner_unicode(sub_node.text) | |
207 | ||
208 | return "" | |
209 | ||
210 | ||
211 | class NetsparkerCloudPlugin(PluginXMLFormat): | |
212 | """ | |
213 | Example plugin to parse netsparkercloud output. | |
214 | """ | |
215 | ||
216 | def __init__(self): | |
217 | super().__init__() | |
218 | self.identifier_tag = "netsparker-cloud" | |
219 | self.id = "NetsparkerCloud" | |
220 | self.name = "NetsparkerCloud XML Output Plugin" | |
221 | self.plugin_version = "0.0.1" | |
222 | self.version = "NetsparkerCloud" | |
223 | self.framework_version = "1.0.0" | |
224 | 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 | ||
237 | def parseOutputString(self, output, debug=False): | |
238 | ||
239 | parser = NetsparkerCloudXmlParser(output) | |
240 | first = True | |
241 | for i in parser.items: | |
242 | if first: | |
243 | ip = self.resolve(i.hostname) | |
244 | 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") | |
252 | ||
253 | first = False | |
254 | ||
255 | 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, | |
257 | request=i.request, response=i.response, resolution=i.resolution, pname=i.param) | |
258 | ||
259 | del parser | |
260 | ||
261 | def processCommandString(self, username, current_path, command_string): | |
262 | return None | |
263 | ||
264 | def setHost(self): | |
265 | pass | |
266 | ||
267 | ||
268 | def createPlugin(): | |
269 | return NetsparkerCloudPlugin() | |
270 | ||
271 | ||
272 | if __name__ == "__main__": | |
273 | import sys | |
274 | import os | |
275 | if len(sys.argv) == 2: | |
276 | report_file = sys.argv[1] | |
277 | if os.path.isfile(report_file): | |
278 | plugin = createPlugin() | |
279 | plugin.processReport(report_file) | |
280 | print(plugin.get_json()) | |
281 | else: | |
282 | print(f"Report not found: {report_file}") | |
283 | else: | |
284 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
285 | # 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 | # 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 PluginXMLFormat | |
7 | ||
8 | import re | |
9 | import os | |
10 | import sys | |
11 | ||
12 | try: | |
13 | import xml.etree.cElementTree as ET | |
14 | import xml.etree.ElementTree as ET_ORIG | |
15 | ETREE_VERSION = ET_ORIG.VERSION | |
16 | except ImportError: | |
17 | import xml.etree.ElementTree as ET | |
18 | ETREE_VERSION = ET.VERSION | |
19 | ||
20 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] | |
21 | ||
22 | current_path = os.path.abspath(os.getcwd()) | |
23 | ||
24 | __author__ = "Micaela Ranea Sanchez" | |
25 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
26 | __credits__ = ["Francisco Amato", "Federico Kirschbaum", | |
27 | "Micaela Ranea Sanchez", "German Riera"] | |
28 | __license__ = "" | |
29 | __version__ = "1.0.0" | |
30 | __maintainer__ = "Micaela Ranea Sanchez" | |
31 | __email__ = "[email protected]" | |
32 | __status__ = "Development" | |
33 | ||
34 | ||
35 | class NexposeFullXmlParser: | |
36 | """ | |
37 | The objective of this class is to parse Nexpose's XML 2.0 Report. | |
38 | ||
39 | TODO: Handle errors. | |
40 | TODO: Test nexpose output version. Handle what happens if the parser doesn't support it. | |
41 | TODO: Test cases. | |
42 | ||
43 | @param xml_filepath A proper xml generated by nexpose | |
44 | """ | |
45 | ||
46 | def __init__(self, xml_output): | |
47 | tree = self.parse_xml(xml_output) | |
48 | self.vulns = self.get_vuln_definitions(tree) | |
49 | ||
50 | if tree: | |
51 | self.items = self.get_items(tree, self.vulns) | |
52 | else: | |
53 | self.items = [] | |
54 | ||
55 | def parse_xml(self, xml_output): | |
56 | """ | |
57 | Open and parse an xml file. | |
58 | ||
59 | TODO: Write custom parser to just read the nodes that we need instead of | |
60 | reading the whole file. | |
61 | ||
62 | @return xml_tree An xml tree instance. None if error. | |
63 | """ | |
64 | try: | |
65 | tree = ET.fromstring(xml_output) | |
66 | except SyntaxError as err: | |
67 | print("SyntaxError: %s. %s" % (err, xml_output)) | |
68 | return None | |
69 | ||
70 | return tree | |
71 | ||
72 | def parse_html_type(self, node: ET.Element) -> str: | |
73 | """ | |
74 | Parse XML element of type HtmlType | |
75 | ||
76 | @return ret A string containing the parsed element | |
77 | """ | |
78 | ret = "" | |
79 | tag: str = node.tag.lower() | |
80 | if tag == 'containerblockelement': | |
81 | if len(list(node)) > 0: | |
82 | for child in list(node): | |
83 | ret += self.parse_html_type(child) | |
84 | else: | |
85 | ret += node.text.strip() if node.text else "" | |
86 | if tag == 'listitem': | |
87 | if len(list(node)) > 0: | |
88 | for child in list(node): | |
89 | ret += self.parse_html_type(child) | |
90 | else: | |
91 | ret = node.text.strip() if node.text else "" | |
92 | if tag == 'orderedlist': | |
93 | i = 1 | |
94 | for item in list(node): | |
95 | ret += "\t" + str(i) + " " + self.parse_html_type(item) + "\n" | |
96 | i += 1 | |
97 | if tag == 'paragraph': | |
98 | if len(list(node)) > 0: | |
99 | for child in list(node): | |
100 | ret += self.parse_html_type(child) | |
101 | else: | |
102 | ret += node.text.strip() if node.text else "" | |
103 | if tag == 'unorderedlist': | |
104 | for item in list(node): | |
105 | ret += "\t" + "* " + self.parse_html_type(item) + "\n" | |
106 | if tag == 'urllink': | |
107 | if node.get('text'): | |
108 | ret += node.text.strip() + " " | |
109 | last = "" | |
110 | for attr in node.attrib: | |
111 | if node.get(attr) and node.get(attr) != node.get(last): | |
112 | ret += node.get(attr) + " " | |
113 | last = attr | |
114 | ||
115 | return ret | |
116 | ||
117 | def parse_tests_type(self, node, vulnsDefinitions): | |
118 | """ | |
119 | Parse XML element of type TestsType | |
120 | ||
121 | @return vulns A list of vulnerabilities according to vulnsDefinitions | |
122 | """ | |
123 | vulns = list() | |
124 | ||
125 | for tests in node.findall('tests'): | |
126 | for test in tests.iter('test'): | |
127 | vuln = dict() | |
128 | if test.get('id').lower() in vulnsDefinitions: | |
129 | vuln = vulnsDefinitions[test.get('id').lower()].copy() | |
130 | key = test.get('key', '') | |
131 | if key.startswith('/'): | |
132 | # It has the path where the vuln was found | |
133 | # Example key: "/comments.asp||content" | |
134 | vuln['path'] = key[:key.find('|')] | |
135 | for desc in list(test): | |
136 | vuln['desc'] += self.parse_html_type(desc) | |
137 | vulns.append(vuln) | |
138 | return vulns | |
139 | ||
140 | def get_vuln_definitions(self, tree): | |
141 | """ | |
142 | @returns vulns A dict of Vulnerability Definitions | |
143 | """ | |
144 | vulns = dict() | |
145 | #CVSS V3 | |
146 | SEVERITY_MAPPING_DICT = {'0': 'info', '1': 'low', '2': 'low', '3': 'low', '4': 'med', '5': 'med', '6': 'med', | |
147 | '7': 'high', '8': 'high', '9': 'critical', '10': 'critical'} | |
148 | ||
149 | for vulnsDef in tree.iter('VulnerabilityDefinitions'): | |
150 | for vulnDef in vulnsDef.iter('vulnerability'): | |
151 | vid = vulnDef.get('id').lower() | |
152 | vector = vulnDef.get('cvssVector') | |
153 | ||
154 | vuln = { | |
155 | 'desc': "", | |
156 | 'name': vulnDef.get('title'), | |
157 | 'refs': ["vector: " + vector, vid], | |
158 | 'resolution': "", | |
159 | 'severity': SEVERITY_MAPPING_DICT[vulnDef.get('severity')], | |
160 | 'tags': list(), | |
161 | 'is_web': vid.startswith('http-') | |
162 | } | |
163 | ||
164 | for item in list(vulnDef): | |
165 | if item.tag == 'description': | |
166 | for htmlType in list(item): | |
167 | vuln['desc'] += self.parse_html_type(htmlType) | |
168 | if item.tag == 'exploits': | |
169 | for exploit in list(item): | |
170 | if exploit.get('title') and exploit.get('link'): | |
171 | title = exploit.get('title').encode( | |
172 | "ascii", errors="backslashreplace").strip() | |
173 | link = exploit.get('link').encode( | |
174 | "ascii", errors="backslashreplace").strip() | |
175 | vuln['refs'].append(title + b' ' + link) | |
176 | if item.tag == 'references': | |
177 | for ref in list(item): | |
178 | if ref.text: | |
179 | rf = ref.text.encode( | |
180 | "ascii", errors="backslashreplace").strip() | |
181 | vuln['refs'].append(rf) | |
182 | if item.tag == 'solution': | |
183 | for htmlType in list(item): | |
184 | vuln[ | |
185 | 'resolution'] += self.parse_html_type(htmlType) | |
186 | """ | |
187 | # there is currently no method to register tags in vulns | |
188 | if item.tag == 'tags': | |
189 | for tag in list(item): | |
190 | vuln['tags'].append(tag.text.lower()) | |
191 | """ | |
192 | vulns[vid] = vuln | |
193 | return vulns | |
194 | ||
195 | def get_items(self, tree, vulns): | |
196 | """ | |
197 | @return hosts A list of Host instances | |
198 | """ | |
199 | ||
200 | hosts = list() | |
201 | ||
202 | for nodes in tree.iter('nodes'): | |
203 | for node in nodes.iter('node'): | |
204 | host = dict() | |
205 | host['name'] = node.get('address') | |
206 | host['hostnames'] = list() | |
207 | host['os'] = "" | |
208 | host['services'] = list() | |
209 | host['vulns'] = self.parse_tests_type(node, vulns) | |
210 | ||
211 | for names in node.iter('names'): | |
212 | for name in list(names): | |
213 | host['hostnames'].append(name.text) | |
214 | ||
215 | for fingerprints in node.iter('fingerprints'): | |
216 | os = fingerprints.find('os') | |
217 | if os is not None: | |
218 | host['os'] = os.get('product', "") | |
219 | if os.get('version') is not None: | |
220 | host['os'] += " " + os.get('version') | |
221 | ||
222 | for endpoints in node.iter('endpoints'): | |
223 | for endpoint in list(endpoints): | |
224 | svc = { | |
225 | 'protocol': endpoint.get('protocol'), | |
226 | 'port': endpoint.get('port'), | |
227 | 'status': endpoint.get('status'), | |
228 | } | |
229 | for services in endpoint.iter('services'): | |
230 | for service in list(services): | |
231 | svc['name'] = service.get('name') | |
232 | svc['vulns'] = self.parse_tests_type( | |
233 | service, vulns) | |
234 | for configs in service.iter('configurations'): | |
235 | for config in list(configs): | |
236 | if "banner" in config.get('name'): | |
237 | svc['version'] = config.get('name') | |
238 | ||
239 | host['services'].append(svc) | |
240 | ||
241 | hosts.append(host) | |
242 | ||
243 | return hosts | |
244 | ||
245 | ||
246 | class NexposeFullPlugin(PluginXMLFormat): | |
247 | """ | |
248 | Example plugin to parse nexpose output. | |
249 | """ | |
250 | ||
251 | def __init__(self): | |
252 | super().__init__() | |
253 | self.identifier_tag = "NexposeReport" | |
254 | self.id = "NexposeFull" | |
255 | self.name = "Nexpose XML 2.0 Report Plugin" | |
256 | self.plugin_version = "0.0.1" | |
257 | self.version = "Nexpose Enterprise 5.7.19" | |
258 | self.framework_version = "1.0.0" | |
259 | self.options = None | |
260 | self._current_output = None | |
261 | self._command_regex = re.compile(r'^(sudo nexpose|\.\/nexpose).*?') | |
262 | ||
263 | ||
264 | def parseOutputString(self, output, debug=False): | |
265 | ||
266 | parser = NexposeFullXmlParser(output) | |
267 | ||
268 | for item in parser.items: | |
269 | ||
270 | h_id = self.createAndAddHost(item['name'], item['os'], hostnames=item['hostnames']) | |
271 | ||
272 | i_id = self.createAndAddInterface( | |
273 | h_id, | |
274 | item['name'], | |
275 | ipv4_address=item['name'], | |
276 | hostname_resolution=item['hostnames']) | |
277 | ||
278 | for v in item['vulns']: | |
279 | ||
280 | v_id = self.createAndAddVulnToHost( | |
281 | h_id, | |
282 | v['name'], | |
283 | v['desc'], | |
284 | v['refs'], | |
285 | v['severity'], | |
286 | v['resolution']) | |
287 | ||
288 | ||
289 | for s in item['services']: | |
290 | web = False | |
291 | version = s.get("version", "") | |
292 | ||
293 | s_id = self.createAndAddServiceToInterface( | |
294 | h_id, | |
295 | i_id, | |
296 | s['name'], | |
297 | s['protocol'], | |
298 | ports=[str(s['port'])], | |
299 | status=s['status'], | |
300 | version=version) | |
301 | ||
302 | for v in s['vulns']: | |
303 | if v['is_web']: | |
304 | v_id = self.createAndAddVulnWebToService( | |
305 | h_id, | |
306 | s_id, | |
307 | v['name'], | |
308 | v['desc'], | |
309 | v['refs'], | |
310 | v['severity'], | |
311 | v['resolution'], | |
312 | path=v.get('path','')) | |
313 | else: | |
314 | v_id = self.createAndAddVulnToService( | |
315 | h_id, | |
316 | s_id, | |
317 | v['name'], | |
318 | v['desc'], | |
319 | v['refs'], | |
320 | v['severity'], | |
321 | v['resolution']) | |
322 | ||
323 | del parser | |
324 | ||
325 | def processCommandString(self, username, current_path, command_string): | |
326 | return None | |
327 | ||
328 | def setHost(self): | |
329 | pass | |
330 | ||
331 | ||
332 | def createPlugin(): | |
333 | return NexposeFullPlugin() | |
334 | ||
335 | if __name__ == "__main__": | |
336 | import sys | |
337 | import os | |
338 | if len(sys.argv) == 2: | |
339 | report_file = sys.argv[1] | |
340 | if os.path.isfile(report_file): | |
341 | plugin = createPlugin() | |
342 | plugin.processReport(report_file) | |
343 | print(plugin.get_json()) | |
344 | else: | |
345 | print(f"Report not found: {report_file}") | |
346 | else: | |
347 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
348 | # 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 | # 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 os | |
7 | import random | |
8 | from html.parser import HTMLParser | |
9 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
10 | from faraday_plugins.plugins import plugins_utils | |
11 | ||
12 | ||
13 | try: | |
14 | import xml.etree.cElementTree as ET | |
15 | import xml.etree.ElementTree as ET_ORIG | |
16 | ETREE_VERSION = ET_ORIG.VERSION | |
17 | except ImportError: | |
18 | import xml.etree.ElementTree as ET | |
19 | ETREE_VERSION = ET.VERSION | |
20 | ||
21 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] | |
22 | ||
23 | current_path = os.path.abspath(os.getcwd()) | |
24 | ||
25 | __author__ = "Francisco Amato" | |
26 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
27 | __credits__ = ["Facundo de Guzmán", "Francisco Amato"] | |
28 | __license__ = "" | |
29 | __version__ = "1.0.0" | |
30 | __maintainer__ = "Francisco Amato" | |
31 | __email__ = "[email protected]" | |
32 | __status__ = "Development" | |
33 | ||
34 | ||
35 | class NiktoXmlParser: | |
36 | """ | |
37 | The objective of this class is to parse an xml file generated by the nikto tool. | |
38 | ||
39 | TODO: Handle errors. | |
40 | TODO: Test nikto output version. Handle what happens if the parser doesn't support it. | |
41 | TODO: Test cases. | |
42 | ||
43 | @param nikto_xml_filepath A proper xml generated by nikto | |
44 | """ | |
45 | ||
46 | def __init__(self, xml_output): | |
47 | ||
48 | tree = self.parse_xml(xml_output) | |
49 | ||
50 | if tree: | |
51 | self.hosts = [host for host in self.get_hosts(tree)] | |
52 | else: | |
53 | self.hosts = [] | |
54 | ||
55 | def parse_xml(self, xml_output): | |
56 | """ | |
57 | Open and parse an xml file. | |
58 | ||
59 | TODO: Write custom parser to just read the nodes that we need instead of | |
60 | reading the whole file. | |
61 | ||
62 | @return xml_tree An xml tree instance. None if error. | |
63 | """ | |
64 | try: | |
65 | tree = ET.fromstring(xml_output) | |
66 | except SyntaxError as err: | |
67 | print("SyntaxError: %s. %s" % (err, xml_output)) | |
68 | return None | |
69 | ||
70 | return tree | |
71 | ||
72 | def get_hosts(self, tree): | |
73 | """ | |
74 | @return items A list of Host instances | |
75 | """ | |
76 | if tree.find('niktoscan'): | |
77 | for host_node in tree.find('niktoscan').findall('scandetails'): | |
78 | yield Host(host_node) | |
79 | else: | |
80 | for host_node in tree.findall('scandetails'): | |
81 | yield Host(host_node) | |
82 | ||
83 | ||
84 | def get_attrib_from_subnode(xml_node, subnode_xpath_expr, attrib_name): | |
85 | """ | |
86 | Finds a subnode in the item node and the retrieves a value from it | |
87 | ||
88 | @return An attribute value | |
89 | """ | |
90 | global ETREE_VERSION | |
91 | node = None | |
92 | ||
93 | if ETREE_VERSION[0] <= 1 and ETREE_VERSION[1] < 3: | |
94 | ||
95 | match_obj = re.search( | |
96 | "([^\@]+?)\[\@([^=]*?)=\'([^\']*?)\'", subnode_xpath_expr) | |
97 | if match_obj is not None: | |
98 | ||
99 | node_to_find = match_obj.group(1) | |
100 | xpath_attrib = match_obj.group(2) | |
101 | xpath_value = match_obj.group(3) | |
102 | for node_found in xml_node.findall(node_to_find): | |
103 | ||
104 | if node_found.attrib[xpath_attrib] == xpath_value: | |
105 | node = node_found | |
106 | break | |
107 | else: | |
108 | node = xml_node.find(subnode_xpath_expr) | |
109 | ||
110 | else: | |
111 | node = xml_node.find(subnode_xpath_expr) | |
112 | ||
113 | if node is not None: | |
114 | return node.get(attrib_name) | |
115 | ||
116 | return None | |
117 | ||
118 | ||
119 | class Item: | |
120 | """ | |
121 | An abstract representation of a Item | |
122 | ||
123 | TODO: Consider evaluating the attributes lazily | |
124 | TODO: Write what's expected to be present in the nodes | |
125 | TODO: Refactor both Host and the Port clases? | |
126 | ||
127 | @param item_node A item_node taken from an nikto xml tree | |
128 | """ | |
129 | ||
130 | def __init__(self, item_node): | |
131 | ||
132 | self.node = item_node | |
133 | ||
134 | self.osvdbid = [ | |
135 | "OSVDB-ID: " + self.node.get('osvdbid')] if self.node.get('osvdbid') != "0" else [] | |
136 | ||
137 | self.namelink = self.get_text_from_subnode('namelink') | |
138 | self.iplink = self.get_text_from_subnode('iplink') | |
139 | ||
140 | self.id_nikto = self.node.get('id') | |
141 | self.osvdblink = self.node.get('osvdbidlink') | |
142 | self.method = self.node.get('method') | |
143 | ||
144 | self.uri = self.get_uri() | |
145 | self.desc = self.get_desc() | |
146 | self.params = self.get_params(self.uri) | |
147 | ||
148 | def get_uri(self): | |
149 | ||
150 | try: | |
151 | ||
152 | uri = self.get_text_from_subnode('uri') | |
153 | h = HTMLParser.HTMLParser() | |
154 | return h.unescape(uri) | |
155 | ||
156 | except Exception as e: | |
157 | return uri | |
158 | ||
159 | def get_desc(self): | |
160 | ||
161 | desc = self.get_text_from_subnode('description') | |
162 | ||
163 | try: | |
164 | ||
165 | uri_present = desc.split(': ', 1)[0] | |
166 | h = HTMLParser.HTMLParser() | |
167 | if uri_present == h.unescape(self.uri): | |
168 | ||
169 | name = desc.split(': ', 1)[1] | |
170 | if name is not None and name != '': | |
171 | return name | |
172 | ||
173 | return desc | |
174 | ||
175 | except Exception as e: | |
176 | return desc | |
177 | ||
178 | def get_params(self, uri): | |
179 | """Return the paramethers as a string""" | |
180 | try: | |
181 | params = uri.split('?')[1].replace('&', ',') | |
182 | except Exception as e: | |
183 | params = '' | |
184 | ||
185 | return params | |
186 | ||
187 | def get_text_from_subnode(self, subnode_xpath_expr): | |
188 | """ | |
189 | Finds a subnode in the host node and the retrieves a value from it. | |
190 | ||
191 | @return An attribute value | |
192 | """ | |
193 | sub_node = self.node.find(subnode_xpath_expr) | |
194 | if sub_node is not None: | |
195 | return sub_node.text | |
196 | ||
197 | return None | |
198 | ||
199 | def __str__(self): | |
200 | ports = [] | |
201 | for port in self.ports: | |
202 | var = " %s" % port | |
203 | ports.append(var) | |
204 | ports = "\n".join(ports) | |
205 | ||
206 | return "%s, %s, %s [%s], %s\n%s" % (self.hostnames, self.status, | |
207 | self.ipv4_address, self.mac_address, self.os, ports) | |
208 | ||
209 | ||
210 | class Host: | |
211 | """ | |
212 | An abstract representation of a Host | |
213 | ||
214 | @param host_node A host_node taken from an nmap xml tree | |
215 | """ | |
216 | ||
217 | def __init__(self, host_node): | |
218 | ||
219 | self.node = host_node | |
220 | self.targetip = self.node.get('targetip') | |
221 | self.targethostname = self.node.get('targethostname') | |
222 | self.port = self.node.get('targetport') | |
223 | self.targetbanner = self.node.get('targetbanner') | |
224 | self.starttime = self.node.get('starttime') | |
225 | self.sitename = self.node.get('sitename') | |
226 | self.siteip = self.node.get('hostheader') | |
227 | self.items = [item for item in self.get_items()] | |
228 | ||
229 | def get_items(self): | |
230 | """ | |
231 | @return items A list of Host instances | |
232 | """ | |
233 | for item_node in self.node.findall('item'): | |
234 | yield Item(item_node) | |
235 | ||
236 | def __str__(self): | |
237 | ports = [] | |
238 | for port in self.ports: | |
239 | var = " %s" % port | |
240 | ports.append(var) | |
241 | ports = "\n".join(ports) | |
242 | ||
243 | return "%s, %s, %s [%s], %s\n%s" % (self.hostnames, self.status, | |
244 | self.ipv4_address, self.mac_address, self.os, ports) | |
245 | ||
246 | ||
247 | class NiktoPlugin(PluginXMLFormat): | |
248 | """ | |
249 | Example plugin to parse nikto output. | |
250 | """ | |
251 | ||
252 | def __init__(self): | |
253 | super().__init__() | |
254 | self.identifier_tag = "niktoscan" | |
255 | self.id = "Nikto" | |
256 | self.name = "Nikto XML Output Plugin" | |
257 | self.plugin_version = "0.0.2" | |
258 | self.version = "2.1.5" | |
259 | self.options = None | |
260 | self._current_output = None | |
261 | self.parent = None | |
262 | self._command_regex = re.compile( | |
263 | r'^(sudo nikto|nikto|sudo nikto\.pl|nikto\.pl|perl nikto\.pl|\.\/nikto\.pl|\.\/nikto).*?') | |
264 | self._completition = { | |
265 | "": "", | |
266 | "-ask+": "Whether to ask about submitting updates", | |
267 | "-Cgidirs+": 'Scan these CGI dirs: "none", "all", or values like "/cgi/ /cgi-a/"', | |
268 | "-config+": "Use this config file", | |
269 | "-Display+": "Turn on/off display outputs:", | |
270 | "-dbcheck": "Check database and other key files for syntax errors", | |
271 | "-evasion+": "Encoding technique:", | |
272 | "-Format+": "Save file (-o) format:", | |
273 | "-Help": "Extended help information", | |
274 | "-host+": "Target host", | |
275 | "-IgnoreCode": "Ignore Codes--treat as negative responses", | |
276 | "-id+": "Host authentication to use, format is id:pass or id:pass:realm", | |
277 | "-key+": "Client certificate key file", | |
278 | "-list-plugins": "List all available plugins, perform no testing", | |
279 | "-maxtime+": "Maximum testing time per host", | |
280 | "-mutate+": "Guess additional file names:", | |
281 | "-mutate-options": "Provide information for mutates", | |
282 | "-nointeractive": "Disables interactive features", | |
283 | "-nolookup": "Disables DNS lookups", | |
284 | "-nossl": "Disables the use of SSL", | |
285 | "-no404": "Disables nikto attempting to guess a 404 page", | |
286 | "-output+": "Write output to this file ('.' for auto-name)", | |
287 | "-Pause+": "Pause between tests (seconds, integer or float)", | |
288 | "-Plugins+": "List of plugins to run (default: ALL)", | |
289 | "-port+": "Port to use (default 80)", | |
290 | "-RSAcert+": "Client certificate file", | |
291 | "-root+": "Prepend root value to all requests, format is /directory", | |
292 | "-Save": "Save positive responses to this directory ('.' for auto-name)", | |
293 | "-ssl": "Force ssl mode on port", | |
294 | "-Tuning+": "Scan tuning:", | |
295 | "-timeout+": "Timeout for requests (default 10 seconds)", | |
296 | "-Userdbs": "Load only user databases, not the standard databases", | |
297 | "-until": "Run until the specified time or duration", | |
298 | "-update": "Update databases and plugins from CIRT.net", | |
299 | "-useproxy": "Use the proxy defined in nikto.conf", | |
300 | "-Version": "Print plugin and database versions", | |
301 | "-vhost+": "Virtual host (for Host header)", | |
302 | } | |
303 | ||
304 | ||
305 | ||
306 | def parseOutputString(self, output, debug=False): | |
307 | """ | |
308 | This method will discard the output the shell sends, it will read it from | |
309 | the xml where it expects it to be present. | |
310 | ||
311 | NOTE: if 'debug' is true then it is being run from a test case and the | |
312 | output being sent is valid. | |
313 | """ | |
314 | ||
315 | parser = NiktoXmlParser(output) | |
316 | ||
317 | for host in parser.hosts: | |
318 | ||
319 | h_id = self.createAndAddHost( | |
320 | host.targetip, | |
321 | hostnames=[host.targethostname] | |
322 | ) | |
323 | ||
324 | s_id = self.createAndAddServiceToHost( | |
325 | h_id, | |
326 | "http", | |
327 | "tcp", | |
328 | ports=[host.port], | |
329 | status="open" | |
330 | ) | |
331 | ||
332 | for item in host.items: | |
333 | ||
334 | v_id = self.createAndAddVulnWebToService( | |
335 | h_id, | |
336 | s_id, | |
337 | name=item.desc, | |
338 | ref=item.osvdbid, | |
339 | method=item.method, | |
340 | params=', '.join(item.params), | |
341 | **plugins_utils.get_vulnweb_url_fields(item.namelink) | |
342 | ) | |
343 | ||
344 | del parser | |
345 | ||
346 | xml_arg_re = re.compile(r"^.*(-output\s*[^\s]+).*$") | |
347 | ||
348 | def processCommandString(self, username, current_path, command_string): | |
349 | """ | |
350 | Adds the -oX parameter to get xml output to the command string that the | |
351 | user has set. | |
352 | """ | |
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 | ) | |
361 | ||
362 | arg_match = self.xml_arg_re.match(command_string) | |
363 | ||
364 | 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) | |
368 | else: | |
369 | 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) | |
373 | ||
374 | def setHost(self): | |
375 | pass | |
376 | ||
377 | ||
378 | def createPlugin(): | |
379 | return NiktoPlugin() | |
380 | ||
381 | if __name__ == "__main__": | |
382 | import sys | |
383 | import os | |
384 | if len(sys.argv) == 2: | |
385 | report_file = sys.argv[1] | |
386 | if os.path.isfile(report_file): | |
387 | plugin = createPlugin() | |
388 | plugin.processReport(report_file) | |
389 | print(plugin.get_json()) | |
390 | else: | |
391 | print(f"Report not found: {report_file}") | |
392 | else: | |
393 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
394 | # 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 | # 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 PluginXMLFormat | |
8 | import re | |
9 | import os | |
10 | import sys | |
11 | import random | |
12 | ||
13 | ||
14 | try: | |
15 | import xml.etree.cElementTree as ET | |
16 | import xml.etree.ElementTree as ET_ORIG | |
17 | ETREE_VERSION = ET_ORIG.VERSION | |
18 | except ImportError: | |
19 | import xml.etree.ElementTree as ET | |
20 | ETREE_VERSION = ET.VERSION | |
21 | ||
22 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] | |
23 | ||
24 | current_path = os.path.abspath(os.getcwd()) | |
25 | ||
26 | ||
27 | class NmapXmlParser: | |
28 | """ | |
29 | The objective of this class is to parse an xml file generated by | |
30 | the nmap tool. | |
31 | ||
32 | TODO: Handle errors. | |
33 | TODO: Test nmap output version. Handle what happens if the parser | |
34 | doesn't support it. | |
35 | TODO: Test cases. | |
36 | ||
37 | @param nmap_xml_filepath A proper xml generated by nmap | |
38 | """ | |
39 | ||
40 | def __init__(self, xml_output): | |
41 | tree = self.parse_xml(xml_output) | |
42 | ||
43 | if tree: | |
44 | self.hosts = [host for host in self.get_hosts(tree)] | |
45 | else: | |
46 | self.hosts = [] | |
47 | ||
48 | def parse_xml(self, xml_output): | |
49 | """ | |
50 | Open and parse an xml file. | |
51 | ||
52 | TODO: Write custom parser to just read the nodes that we need instead | |
53 | of reading the whole file. | |
54 | ||
55 | @return xml_tree An xml tree instance. None if error. | |
56 | """ | |
57 | ||
58 | try: | |
59 | return ET.fromstring(xml_output) | |
60 | except SyntaxError as err: | |
61 | #logger.error("SyntaxError: %s." % (err)) | |
62 | return None | |
63 | ||
64 | def get_hosts(self, tree): | |
65 | """ | |
66 | @return hosts A list of Host instances | |
67 | """ | |
68 | for host_node in tree.findall('host'): | |
69 | yield Host(host_node) | |
70 | ||
71 | ||
72 | def get_attrib_from_subnode(xml_node, subnode_xpath_expr, attrib_name): | |
73 | """ | |
74 | Finds a subnode in the host node and the retrieves a value from it | |
75 | ||
76 | @return An attribute value | |
77 | """ | |
78 | global ETREE_VERSION | |
79 | node = None | |
80 | ||
81 | if ETREE_VERSION[0] <= 1 and ETREE_VERSION[1] < 3: | |
82 | ||
83 | match_obj = re.search( | |
84 | "([^\@]+?)\[\@([^=]*?)=\'([^\']*?)\'", | |
85 | subnode_xpath_expr) | |
86 | ||
87 | if match_obj is not None: | |
88 | ||
89 | node_to_find = match_obj.group(1) | |
90 | xpath_attrib = match_obj.group(2) | |
91 | xpath_value = match_obj.group(3) | |
92 | ||
93 | for node_found in xml_node.findall(node_to_find): | |
94 | if node_found.attrib[xpath_attrib] == xpath_value: | |
95 | node = node_found | |
96 | break | |
97 | else: | |
98 | node = xml_node.find(subnode_xpath_expr) | |
99 | ||
100 | else: | |
101 | node = xml_node.find(subnode_xpath_expr) | |
102 | ||
103 | if node is not None: | |
104 | return node.get(attrib_name) | |
105 | ||
106 | return None | |
107 | ||
108 | ||
109 | class Host: | |
110 | """ | |
111 | An abstract representation of a Host | |
112 | ||
113 | TODO: Consider evaluating the attributes lazily | |
114 | TODO: Write what's expected to be present in the nodes | |
115 | TODO: Refactor both Host and the Port clases? | |
116 | ||
117 | @param host_node A host_node taken from an nmap xml tree | |
118 | """ | |
119 | ||
120 | def __init__(self, host_node): | |
121 | self.node = host_node | |
122 | ||
123 | self.hostnames = [hostname[0] for hostname in self.get_hostnames()] | |
124 | if len(self.hostnames) != 0: | |
125 | self.hostname = self.hostnames[0] | |
126 | else: | |
127 | self.hostname = 'unknown' | |
128 | ||
129 | self.hostnames = list(set(self.hostnames)) | |
130 | self.status = self.get_status() | |
131 | self.ipv4_address = self.get_ipv4_address() | |
132 | self.ipv6_address = self.get_ipv6_address() | |
133 | self.mac_address = self.get_mac_address() | |
134 | self.os_guesses = [os_guess for os_guess in self.get_os_guesses()] | |
135 | self.os = self.top_os_guess() | |
136 | self.ports = [port for port in self.get_ports()] | |
137 | self.vulns = [vuln for vuln in self.get_scripts()] | |
138 | if self.os != 'unknown': | |
139 | for p in self.ports: | |
140 | if p.service is not None: | |
141 | if p.service.ostype: | |
142 | self.os = p.service.ostype | |
143 | break | |
144 | ||
145 | def get_hostnames(self): | |
146 | """ | |
147 | Expects to find one or more | |
148 | '<hostname name="localhost.localdomain" type="PTR"/>' in the host node. | |
149 | ||
150 | @return A list of (hostname, hostname_type) or None | |
151 | """ | |
152 | for hostname in self.node.findall('hostnames/hostname'): | |
153 | yield (hostname.attrib["name"], hostname.attrib["type"]) | |
154 | ||
155 | def get_attrib_from_subnode(self, subnode_xpath_expr, attrib_name): | |
156 | """ | |
157 | Finds a subnode in the host node and the retrieves a value from it | |
158 | ||
159 | @return An attribute value | |
160 | """ | |
161 | return get_attrib_from_subnode( | |
162 | self.node, | |
163 | subnode_xpath_expr, | |
164 | attrib_name) | |
165 | ||
166 | def get_status(self): | |
167 | """ | |
168 | Expects to find '<status state="up" reason="conn-refused"/>' | |
169 | in the node | |
170 | TODO: Use 'reason' | |
171 | @return An status or 'unknown' | |
172 | """ | |
173 | status = self.get_attrib_from_subnode('status', 'state') | |
174 | ||
175 | return status if status else 'unknown' | |
176 | ||
177 | def get_ipv4_address(self): | |
178 | """ | |
179 | Expects to find '<address addr="127.0.0.1" addrtype="ipv4"/>' | |
180 | in the node | |
181 | ||
182 | @return ip_address or 'unknown' | |
183 | """ | |
184 | ip_address = self.get_attrib_from_subnode( | |
185 | "address[@addrtype='ipv4']", | |
186 | 'addr') | |
187 | return ip_address if ip_address else 'unknown' | |
188 | ||
189 | def get_ipv6_address(self): | |
190 | """ | |
191 | Expects to find '<address addr="127.0.0.1" addrtype="ipv6"/>' | |
192 | in the node | |
193 | ||
194 | @return ip_address or 'unknown' | |
195 | """ | |
196 | ip_address = self.get_attrib_from_subnode( | |
197 | "address[@addrtype='ipv6']", | |
198 | 'addr') | |
199 | return ip_address if ip_address else 'unknown' | |
200 | ||
201 | def get_mac_address(self): | |
202 | """ | |
203 | Expects to find | |
204 | '<address addr="00:08:54:26:A9:E5" addrtype="mac" vendor="Netronix" />' | |
205 | in the node | |
206 | ||
207 | @return mac_address or 'unknown' | |
208 | """ | |
209 | mac_address = self.get_attrib_from_subnode( | |
210 | "address[@addrtype='mac']", | |
211 | 'addr') | |
212 | return mac_address if mac_address else 'unknown' | |
213 | ||
214 | def get_os_guesses(self): | |
215 | """ | |
216 | Expects to find | |
217 | '<os>..<osclass type="general purpose" vendor="Microsoft" | |
218 | osfamily="Windows" osgen="2003" accuracy="96" />..</os>' in the node | |
219 | ||
220 | @return A list of (os_vendor_family_gen, accuracy) | |
221 | """ | |
222 | # OS information about host with great acurracy. | |
223 | ||
224 | osclasses = self.node.findall('os/osclass') | |
225 | if osclasses == []: | |
226 | osclasses = self.node.findall('os/osmatch/osclass') | |
227 | ||
228 | for osclass in osclasses: | |
229 | os_vendor = osclass.get("vendor", "unknown") | |
230 | os_family = osclass.get("osfamily", "unknown") | |
231 | os_gen = osclass.get("osgen", "unknown") | |
232 | accuracy = osclass.get("accuracy", "unknown") | |
233 | ||
234 | yield ("%s %s %s" % (os_vendor, os_family, os_gen), accuracy) | |
235 | ||
236 | # Os information in services, bad acurracy. | |
237 | if osclasses == []: | |
238 | services = self.node.findall("ports/port/service") | |
239 | for service in services: | |
240 | ostype = service.get("ostype", "unknown") | |
241 | yield ("%s" % ostype, 0) | |
242 | ||
243 | ||
244 | def top_os_guess(self): | |
245 | """ | |
246 | @return The most accurate os_guess_id or 'unknown'. | |
247 | """ | |
248 | return self.os_guesses[0][0] if len(self.os_guesses) != 0 else 'unknown' | |
249 | ||
250 | def get_scripts(self): | |
251 | # Expects to find a scripts in the node. | |
252 | for s in self.node.findall('hostscript/script'): | |
253 | yield Script(s) | |
254 | ||
255 | def get_ports(self): | |
256 | """ | |
257 | Expects to find one or more | |
258 | '<port protocol="tcp" portid="631">...</port>' in the node. | |
259 | ||
260 | @return A list of Port instances or None | |
261 | """ | |
262 | for port in self.node.findall('ports/port'): | |
263 | yield Port(port) | |
264 | ||
265 | def is_up(self): | |
266 | """ | |
267 | Returns True if the host is up else False. | |
268 | """ | |
269 | if self.status == 'up': | |
270 | return True | |
271 | else: | |
272 | return False | |
273 | ||
274 | def __str__(self): | |
275 | ports = [] | |
276 | for port in self.ports: | |
277 | var = " %s" % port | |
278 | ports.append(var) | |
279 | ports = "\n".join(ports) | |
280 | ||
281 | return "%s, %s, %s [%s], %s\n%s" % ( | |
282 | self.hostnames, | |
283 | self.status, | |
284 | self.ipv4_address, | |
285 | self.mac_address, | |
286 | self.os, ports) | |
287 | ||
288 | ||
289 | class Port: | |
290 | """ | |
291 | An abstract representation of a Port. | |
292 | ||
293 | @param port_node A port_node taken from an nmap xml tree | |
294 | """ | |
295 | ||
296 | def __init__(self, port_node): | |
297 | self.node = port_node | |
298 | ||
299 | self.protocol = self.node.get("protocol") | |
300 | self.number = self.node.get("portid") | |
301 | self.state, self.reason, self.reason_ttl = self.get_state() | |
302 | self.service = self.get_service() | |
303 | self.vulns = [vuln for vuln in self.get_scripts()] | |
304 | ||
305 | def get_attrib_from_subnode(self, subnode_xpath_expr, attrib_name): | |
306 | """ | |
307 | Finds a subnode in the host node and the retrieves a value from it. | |
308 | ||
309 | @return An attribute value | |
310 | """ | |
311 | return get_attrib_from_subnode( | |
312 | self.node, | |
313 | subnode_xpath_expr, | |
314 | attrib_name) | |
315 | ||
316 | def get_state(self): | |
317 | """ | |
318 | Expects to find a | |
319 | '<state state="open" reason="syn-ack" reason_ttl="0"/>' in the node. | |
320 | ||
321 | @return (state, reason, reason_ttl) or ('unknown','unknown','unknown') | |
322 | """ | |
323 | state = self.get_attrib_from_subnode('state', 'state') | |
324 | reason = self.get_attrib_from_subnode('state', 'reason') | |
325 | reason_ttl = self.get_attrib_from_subnode('state', 'reason_ttl') | |
326 | ||
327 | return (state if state else 'unknown', | |
328 | reason if reason else 'unknown', | |
329 | reason_ttl if reason_ttl else 'unknown') | |
330 | ||
331 | def get_service(self): | |
332 | """ | |
333 | Expects to find a service in the node. | |
334 | """ | |
335 | service_node = self.node.find('service') | |
336 | if service_node is not None: | |
337 | return Service(service_node) | |
338 | ||
339 | return None | |
340 | ||
341 | def get_scripts(self): | |
342 | """ | |
343 | Expects to find a scripts in the node. | |
344 | """ | |
345 | for s in self.node.findall('script'): | |
346 | yield Script(s) | |
347 | ||
348 | def __str__(self): | |
349 | return "%s, %s, Service: %s" % (self.number, self.state, self.service) | |
350 | ||
351 | ||
352 | class Script: | |
353 | """ | |
354 | An abstract representation of a Script. | |
355 | ||
356 | '<script id="http-methods" output="No Allow or Public header in OPTIONS | |
357 | response (status code 400)"/><script id="http-title" | |
358 | output="Document Error: Unauthorized"><elem key="title"> | |
359 | Document Error: Unauthorized</elem></script>' | |
360 | ||
361 | @param script_node A script_node taken from an nmap xml tree | |
362 | """ | |
363 | ||
364 | def parse_output(self, output): | |
365 | block_re = re.compile('^\s{4}References:((?:.|[\r\n])+[\r\n](?:\s{4}\w|\s*$))', re.MULTILINE) | |
366 | m1 = block_re.findall(output) | |
367 | if len(m1) > 0: | |
368 | links_re = re.compile('[ \t]+([^ \t\n\r]+)[ \t]*') | |
369 | m2 = links_re.findall(m1[0]) | |
370 | return m2 | |
371 | return [] | |
372 | ||
373 | def __init__(self, script_node): | |
374 | self.node = script_node | |
375 | ||
376 | self.name = script_node.get("id") | |
377 | self.desc = script_node.get("output") | |
378 | self.refs = self.parse_output(self.desc) | |
379 | self.response = "" | |
380 | for k in script_node.findall("elem"): | |
381 | self.response += "\n" + str(k.get('key')) + ": " + str(k.text) | |
382 | self.web = re.search("(http-|https-)", self.name) | |
383 | ||
384 | def __str__(self): | |
385 | return "%s, %s, %s" % (self.name, self.product, self.version) | |
386 | ||
387 | ||
388 | class Service: | |
389 | """ | |
390 | An abstract representation of a Service. | |
391 | ||
392 | '<service name="ipp" product="CUPS" version="1.4" method="probed" | |
393 | conf="10"/>' | |
394 | ||
395 | @param service_node A service_node taken from an nmap xml tree | |
396 | """ | |
397 | ||
398 | def __init__(self, service_node): | |
399 | self.node = service_node | |
400 | ||
401 | name = service_node.get("name") | |
402 | self.name = name if name else 'unknown' | |
403 | ||
404 | product = service_node.get("product") | |
405 | self.product = product if product else 'unknown' | |
406 | ||
407 | version = service_node.get("version") | |
408 | self.version = version if version else 'unknown' | |
409 | ||
410 | self.method = service_node.get("method") | |
411 | self.conf = service_node.get("conf") | |
412 | self.ostype = self.node.get("ostype") | |
413 | ||
414 | def __str__(self): | |
415 | return "%s, %s, %s" % (self.name, self.product, self.version) | |
416 | ||
417 | ||
418 | class NmapPlugin(PluginXMLFormat): | |
419 | """ | |
420 | Example plugin to parse nmap output. | |
421 | """ | |
422 | ||
423 | def __init__(self): | |
424 | super().__init__() | |
425 | self.identifier_tag = "nmaprun" | |
426 | self.id = "Nmap" | |
427 | self.name = "Nmap XML Output Plugin" | |
428 | self.plugin_version = "0.0.3" | |
429 | self.version = "6.40" | |
430 | self.framework_version = "1.0.0" | |
431 | self.options = None | |
432 | self._current_output = None | |
433 | self._command_regex = re.compile(r'^(sudo nmap|nmap|\.\/nmap).*?') | |
434 | ||
435 | ||
436 | ||
437 | self.xml_arg_re = re.compile(r"^.*(-oX\s*[^\s]+).*$") | |
438 | self.addSetting("Scan Technique", str, "-sS") | |
439 | ||
440 | def parseOutputString(self, output, debug=False): | |
441 | """ | |
442 | This method will discard the output the shell sends, it will read it | |
443 | from the xml where it expects it to be present. | |
444 | ||
445 | NOTE: if 'debug' is true then it is being run from a test case and the | |
446 | output being sent is valid. | |
447 | """ | |
448 | ||
449 | parser = NmapXmlParser(output) | |
450 | ||
451 | for host in parser.hosts: | |
452 | # if not host.is_up(): | |
453 | # continue | |
454 | ||
455 | if host.mac_address == 'unknown': | |
456 | host.mac_address = "00:00:00:00:00:00" | |
457 | ||
458 | if host.ipv4_address != 'unknown': | |
459 | minterfase = host.ipv4_address | |
460 | h_id = self.createAndAddHost(minterfase, host.os) | |
461 | i_id = self.createAndAddInterface( | |
462 | h_id, | |
463 | minterfase, | |
464 | host.mac_address, | |
465 | ipv4_address=host.ipv4_address, | |
466 | hostname_resolution=host.hostnames) | |
467 | else: | |
468 | minterfase = host.ipv6_address | |
469 | h_id = self.createAndAddHost(minterfase, host.os) | |
470 | i_id = self.createAndAddInterface( | |
471 | h_id, | |
472 | minterfase, | |
473 | host.mac_address, | |
474 | ipv6_address=host.ipv6_address, | |
475 | hostname_resolution=host.hostnames) | |
476 | ||
477 | for v in host.vulns: | |
478 | desc = v.desc | |
479 | desc += "\nOutput: " + v.response if v.response else "" | |
480 | ||
481 | v_id = self.createAndAddVulnToHost( | |
482 | h_id, | |
483 | v.name, | |
484 | desc=v.desc, | |
485 | ref=v.refs, | |
486 | severity=0, | |
487 | external_id=v.name) | |
488 | ||
489 | for port in host.ports: | |
490 | ||
491 | srvname = str(port.number) | |
492 | srvversion = "unknown" | |
493 | if port.service is not None: | |
494 | srvname = port.service.name | |
495 | srvversion = port.service.product if port.service.product != "unknown" else "" | |
496 | srvversion += " " + port.service.version if port.service.version != "unknown" else "" | |
497 | ||
498 | s_id = self.createAndAddServiceToInterface( | |
499 | h_id, | |
500 | i_id, | |
501 | srvname, | |
502 | port.protocol, | |
503 | ports=[port.number], | |
504 | status=port.state, | |
505 | version=srvversion, | |
506 | description=srvname) | |
507 | ||
508 | for v in port.vulns: | |
509 | severity = 0 | |
510 | desc = v.desc | |
511 | refs = v.refs | |
512 | ||
513 | if re.search(r"VULNERABLE", desc): | |
514 | severity = "high" | |
515 | if re.search(r"ERROR", desc): | |
516 | severity = "unclassified" | |
517 | if re.search(r"Couldn't", desc): | |
518 | severity = "unclassified" | |
519 | if v.web: | |
520 | v_id = self.createAndAddVulnWebToService( | |
521 | h_id, | |
522 | s_id, | |
523 | v.name, | |
524 | desc=desc, | |
525 | response = v.response if v.response else "", | |
526 | ref=refs, | |
527 | severity=severity, | |
528 | website=minterfase, | |
529 | external_id=v.name) | |
530 | else: | |
531 | v_id = self.createAndAddVulnToService( | |
532 | h_id, | |
533 | s_id, | |
534 | v.name, | |
535 | desc=v.desc, | |
536 | ref=refs, | |
537 | severity=severity, | |
538 | external_id=v.name) | |
539 | del parser | |
540 | return True | |
541 | ||
542 | def processCommandString(self, username, current_path, command_string): | |
543 | """ | |
544 | Adds the -oX parameter to get xml output to the command string that the | |
545 | user has set. | |
546 | """ | |
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 | ||
556 | arg_match = self.xml_arg_re.match(command_string) | |
557 | ||
558 | if arg_match is None: | |
559 | return re.sub(r"(^.*?nmap)", | |
560 | r"\1 -oX %s" % self._output_file_path, | |
561 | command_string) | |
562 | else: | |
563 | return re.sub(arg_match.group(1), | |
564 | r"-oX %s" % self._output_file_path, | |
565 | command_string) | |
566 | ||
567 | def setHost(self): | |
568 | pass | |
569 | ||
570 | ||
571 | def createPlugin(): | |
572 | return NmapPlugin() | |
573 | ||
574 | if __name__ == "__main__": | |
575 | import sys | |
576 | import os | |
577 | if len(sys.argv) == 2: | |
578 | report_file = sys.argv[1] | |
579 | if os.path.isfile(report_file): | |
580 | plugin = createPlugin() | |
581 | plugin.processReport(report_file) | |
582 | print(plugin.get_json()) | |
583 | else: | |
584 | print(f"Report not found: {report_file}") | |
585 | else: | |
586 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
587 | # 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 | # 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 | import re | |
7 | import os | |
8 | from collections import defaultdict | |
9 | ||
10 | try: | |
11 | import xml.etree.cElementTree as ET | |
12 | import xml.etree.ElementTree as ET_ORIG | |
13 | ETREE_VERSION = ET_ORIG.VERSION | |
14 | except ImportError: | |
15 | import xml.etree.ElementTree as ET | |
16 | ETREE_VERSION = ET.VERSION | |
17 | ||
18 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
19 | from faraday_plugins.plugins.plugins_utils import filter_services | |
20 | ||
21 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] | |
22 | ||
23 | current_path = os.path.abspath(os.getcwd()) | |
24 | ||
25 | __author__ = "Francisco Amato" | |
26 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
27 | __credits__ = ["Francisco Amato"] | |
28 | __license__ = "" | |
29 | __version__ = "1.0.0" | |
30 | __maintainer__ = "Francisco Amato" | |
31 | __email__ = "[email protected]" | |
32 | __status__ = "Development" | |
33 | ||
34 | ||
35 | class OpenvasXmlParser: | |
36 | """ | |
37 | The objective of this class is to parse an xml file generated by the openvas tool. | |
38 | ||
39 | TODO: Handle errors. | |
40 | TODO: Test openvas output version. Handle what happens if the parser doesn't support it. | |
41 | TODO: Test cases. | |
42 | ||
43 | @param openvas_xml_filepath A proper xml generated by openvas | |
44 | """ | |
45 | ||
46 | def __init__(self, xml_output, logger): | |
47 | self.target = None | |
48 | self.port = "80" | |
49 | self.host = None | |
50 | self.logger = logger | |
51 | tree = self.parse_xml(xml_output) | |
52 | if tree: | |
53 | self.hosts = self.get_hosts(tree) | |
54 | self.items = list(self.get_items(tree, self.hosts)) | |
55 | else: | |
56 | self.items = [] | |
57 | ||
58 | def parse_xml(self, xml_output): | |
59 | """ | |
60 | Open and parse an xml file. | |
61 | ||
62 | TODO: Write custom parser to just read the nodes that we need instead of | |
63 | reading the whole file. | |
64 | ||
65 | @return xml_tree An xml tree instance. None if error. | |
66 | """ | |
67 | try: | |
68 | tree = ET.fromstring(xml_output) | |
69 | except SyntaxError as err: | |
70 | self.logger.error("SyntaxError: %s. %s", err, xml_output) | |
71 | return None | |
72 | return tree | |
73 | ||
74 | def get_items(self, tree, hosts): | |
75 | """ | |
76 | @return items A list of Host instances | |
77 | """ | |
78 | try: | |
79 | report = tree.find('report') | |
80 | results = report.findall('results') | |
81 | if results: | |
82 | nodes = report.findall('results')[0] | |
83 | else: | |
84 | nodes = tree.findall('result') | |
85 | for node in nodes: | |
86 | try: | |
87 | yield Item(node, hosts) | |
88 | except Exception as e: | |
89 | self.logger.error("Error generating Item from %s [%s]", node.attrib, e) | |
90 | except Exception as e: | |
91 | self.logger.error("Tag not found: %s", e) | |
92 | ||
93 | def get_hosts(self, tree): | |
94 | # Hosts are located in: /report/report/host | |
95 | # hosts_dict will contain has keys its details and its hostnames | |
96 | hosts = tree.findall('report/host') | |
97 | hosts_dict = {} | |
98 | for host in hosts: | |
99 | ip = self.do_clean(host.find('ip').text) | |
100 | details = self.get_data_from_detail(host.findall('detail')) | |
101 | hosts_dict[ip] = details | |
102 | return hosts_dict | |
103 | ||
104 | def get_data_from_detail(self, details): | |
105 | data = {} | |
106 | details_data = defaultdict(list) | |
107 | hostnames = [] | |
108 | for item in details: | |
109 | name = self.do_clean(item.find('name').text) | |
110 | value = self.do_clean(item.find('value').text) | |
111 | if "EXIT" in name: | |
112 | continue | |
113 | if name == 'hostname': | |
114 | hostnames.append(value) | |
115 | else: | |
116 | details_data[name].append(value) | |
117 | data['details'] = details_data | |
118 | data['hostnames'] = hostnames | |
119 | return data | |
120 | ||
121 | def do_clean(self, value): | |
122 | myreturn = "" | |
123 | if value is not None: | |
124 | myreturn = re.sub("\s+", " ", value) | |
125 | return myreturn.strip() | |
126 | ||
127 | ||
128 | def get_attrib_from_subnode(xml_node, subnode_xpath_expr, attrib_name): | |
129 | """ | |
130 | Finds a subnode in the item node and the retrieves a value from it | |
131 | ||
132 | @return An attribute value | |
133 | """ | |
134 | global ETREE_VERSION | |
135 | node = None | |
136 | ||
137 | if ETREE_VERSION[0] <= 1 and ETREE_VERSION[1] < 3: | |
138 | ||
139 | match_obj = re.search( | |
140 | "([^\@]+?)\[\@([^=]*?)=\'([^\']*?)\'", | |
141 | subnode_xpath_expr) | |
142 | ||
143 | if match_obj is not None: | |
144 | node_to_find = match_obj.group(1) | |
145 | xpath_attrib = match_obj.group(2) | |
146 | xpath_value = match_obj.group(3) | |
147 | for node_found in xml_node.findall(node_to_find): | |
148 | if node_found.attrib[xpath_attrib] == xpath_value: | |
149 | node = node_found | |
150 | break | |
151 | else: | |
152 | node = xml_node.find(subnode_xpath_expr) | |
153 | ||
154 | else: | |
155 | node = xml_node.find(subnode_xpath_expr) | |
156 | ||
157 | if node is not None: | |
158 | return node.get(attrib_name) | |
159 | ||
160 | return None | |
161 | ||
162 | ||
163 | class Item: | |
164 | """ | |
165 | An abstract representation of a Item | |
166 | @param item_node A item_node taken from an openvas xml tree | |
167 | """ | |
168 | ||
169 | def __init__(self, item_node, hosts): | |
170 | self.node = item_node | |
171 | self.host = self.get_text_from_subnode('host') | |
172 | self.subnet = self.get_text_from_subnode('subnet') | |
173 | if self.subnet == '': | |
174 | self.subnet = self.host | |
175 | self.port = "None" | |
176 | self.severity = self.severity_mapper() | |
177 | self.service = "Unknown" | |
178 | self.protocol = "" | |
179 | port = self.get_text_from_subnode('port') | |
180 | ||
181 | if "general" not in port: | |
182 | # service vuln | |
183 | info = port.split("/") | |
184 | self.port = info[0] | |
185 | self.protocol = info[1] | |
186 | host_details = hosts[self.host].get('details') | |
187 | self.service = self.get_service(port, host_details) | |
188 | else: | |
189 | # general was found in port data | |
190 | # this is a host vuln | |
191 | # this case will have item.port = 'None' | |
192 | info = port.split("/") | |
193 | self.protocol = info[1] | |
194 | self.service = info[0] # this value is general | |
195 | self.nvt = self.node.findall('nvt')[0] | |
196 | self.node = self.nvt | |
197 | self.id = self.node.get('oid') | |
198 | self.name = self.get_text_from_subnode('name') | |
199 | self.cve = self.get_text_from_subnode('cve') if self.get_text_from_subnode('cve') != "NOCVE" else "" | |
200 | self.bid = self.get_text_from_subnode('bid') if self.get_text_from_subnode('bid') != "NOBID" else "" | |
201 | self.xref = self.get_text_from_subnode('xref') if self.get_text_from_subnode('xref') != "NOXREF" else "" | |
202 | self.description = '' | |
203 | self.resolution = '' | |
204 | self.cvss_vector = '' | |
205 | self.tags = self.get_text_from_subnode('tags') | |
206 | if self.tags: | |
207 | tags_data = self.get_data_from_tags(self.tags) | |
208 | self.description = tags_data['description'] | |
209 | self.resolution = tags_data['solution'] | |
210 | self.cvss_vector = tags_data['cvss_base_vector'] | |
211 | ||
212 | ||
213 | def get_text_from_subnode(self, subnode_xpath_expr): | |
214 | """ | |
215 | Finds a subnode in the host node and the retrieves a value from it. | |
216 | ||
217 | @return An attribute value | |
218 | """ | |
219 | sub_node = self.node.find(subnode_xpath_expr) | |
220 | if sub_node is not None and sub_node.text is not None: | |
221 | return sub_node.text.strip() | |
222 | return '' | |
223 | ||
224 | def severity_mapper(self): | |
225 | severity = self.get_text_from_subnode('threat') | |
226 | if severity == 'Alarm': | |
227 | severity = 'Critical' | |
228 | return severity | |
229 | ||
230 | def get_service(self, port, details_from_host): | |
231 | # details_from_host: | |
232 | # name: name of detail | |
233 | # value: list with the values associated with the name | |
234 | for name, value in details_from_host.items(): | |
235 | service_detail = self.get_service_from_details(name, value, port) | |
236 | if service_detail: | |
237 | return service_detail | |
238 | # if the service is not in details_from_host, we will search it in | |
239 | # the file port_mapper.txt | |
240 | services_mapper = filter_services() | |
241 | for service in services_mapper: | |
242 | if service[0] == port: | |
243 | return service[1] | |
244 | ||
245 | return "Unknown" | |
246 | ||
247 | def do_clean(self, value): | |
248 | myreturn = "" | |
249 | if value is not None: | |
250 | myreturn = re.sub("\s+", " ", value) | |
251 | ||
252 | return myreturn.strip() | |
253 | ||
254 | def get_service_from_details(self, name, value_list, port): | |
255 | # detail: | |
256 | # name: name of detail | |
257 | # value_list: list with the values associated with the name | |
258 | res = None | |
259 | priority = 0 | |
260 | ||
261 | for value in value_list: | |
262 | if name == 'Services': | |
263 | aux_port = port.split('/')[0] | |
264 | value_splited = value.split(',') | |
265 | if value_splited[0] == aux_port: | |
266 | res = value_splited[2] | |
267 | priority = 3 | |
268 | ||
269 | elif '/' in value and priority != 3: | |
270 | auxiliar_value = value.split('/')[0] | |
271 | if auxiliar_value == port.split('/')[0]: | |
272 | res = name | |
273 | priority = 2 | |
274 | ||
275 | elif value.isdigit() and priority == 0: | |
276 | if value == port.split('/')[0]: | |
277 | res = name | |
278 | priority = 1 | |
279 | ||
280 | elif '::' in value and priority == 0: | |
281 | aux_value = value.split('::')[0] | |
282 | auxiliar_port = port.split('/')[0] | |
283 | if aux_value == auxiliar_port: | |
284 | res = name | |
285 | return res | |
286 | ||
287 | def get_data_from_tags(self, tags_text): | |
288 | clean_text = self.do_clean(tags_text) | |
289 | tags = clean_text.split('|') | |
290 | summary = '' | |
291 | insight = '' | |
292 | data = { | |
293 | 'solution': '', | |
294 | 'cvss_base_vector': '', | |
295 | 'description': '' | |
296 | } | |
297 | for tag in tags: | |
298 | splited_tag = tag.split('=', 1) | |
299 | if splited_tag[0] in data.keys(): | |
300 | data[splited_tag[0]] = splited_tag[1] | |
301 | elif splited_tag[0] == 'summary': | |
302 | summary = splited_tag[1] | |
303 | elif splited_tag[0] == 'insight': | |
304 | insight = splited_tag[1] | |
305 | ||
306 | data['description'] = ' '.join([summary, insight]).strip() | |
307 | ||
308 | return data | |
309 | ||
310 | ||
311 | class OpenvasPlugin(PluginXMLFormat): | |
312 | """ | |
313 | Example plugin to parse openvas output. | |
314 | """ | |
315 | ||
316 | def __init__(self): | |
317 | super().__init__() | |
318 | self.identifier_tag = "report" | |
319 | self.id = "Openvas" | |
320 | self.name = "Openvas XML Output Plugin" | |
321 | self.plugin_version = "0.3" | |
322 | self.version = "9.0.3" | |
323 | self.framework_version = "1.0.0" | |
324 | self.options = None | |
325 | self._current_output = None | |
326 | self.target = None | |
327 | self._command_regex = re.compile( | |
328 | r'^(openvas|sudo openvas|\.\/openvas).*?') | |
329 | ||
330 | ||
331 | def report_belongs_to(self, **kwargs): | |
332 | if super().report_belongs_to(**kwargs): | |
333 | report_path = kwargs.get("report_path", "") | |
334 | with open(report_path) as f: | |
335 | output = f.read() | |
336 | return re.search("OpenVAS", output) is not None or re.search('<omp>', output) is not None | |
337 | return False | |
338 | ||
339 | def parseOutputString(self, output, debug=False): | |
340 | """ | |
341 | This method will discard the output the shell sends, it will read it | |
342 | from the xml where it expects it to be present. | |
343 | ||
344 | NOTE: if 'debug' is true then it is being run from a test case and the | |
345 | output being sent is valid. | |
346 | """ | |
347 | parser = OpenvasXmlParser(output, self.logger) | |
348 | web = False | |
349 | ids = {} | |
350 | # The following threats values will not be taken as vulns | |
351 | self.ignored_severities = ['Log', 'Debug'] | |
352 | for ip, values in parser.hosts.items(): | |
353 | # values contains: ip details and ip hostnames | |
354 | h_id = self.createAndAddHost( | |
355 | ip, | |
356 | hostnames=values['hostnames'] | |
357 | ) | |
358 | ids[ip] = h_id | |
359 | ||
360 | for item in parser.items: | |
361 | if item.name is not None: | |
362 | ref = [] | |
363 | if item.cve: | |
364 | cves = item.cve.split(',') | |
365 | for cve in cves: | |
366 | ref.append(cve.encode("utf-8").strip()) | |
367 | if item.bid: | |
368 | bids = item.bid.split(',') | |
369 | for bid in bids: | |
370 | ref.append("BID-%s" % bid.encode("utf-8").strip() ) | |
371 | if item.xref: | |
372 | ref.append(item.xref.encode("utf-8")) | |
373 | if item.tags and item.cvss_vector: | |
374 | ref.append(item.cvss_vector.encode("utf-8")) | |
375 | ||
376 | if item.subnet in ids: | |
377 | h_id = ids[item.host] | |
378 | else: | |
379 | h_id = self.createAndAddHost( | |
380 | item.subnet, | |
381 | hostnames=[item.host]) | |
382 | ids[item.subnet] = h_id | |
383 | ||
384 | if item.port == "None": | |
385 | if item.severity not in self.ignored_severities: | |
386 | v_id = self.createAndAddVulnToHost( | |
387 | h_id, | |
388 | item.name, | |
389 | desc=item.description, | |
390 | severity=item.severity, | |
391 | resolution=item.resolution, | |
392 | ref=ref, | |
393 | external_id=item.id) | |
394 | else: | |
395 | if item.service: | |
396 | web = re.search( | |
397 | r'^(www|http)', | |
398 | item.service) | |
399 | else: | |
400 | web = item.port in ('80', '443', '8080') | |
401 | ||
402 | if item.subnet + "_" + item.port in ids: | |
403 | s_id = ids[item.subnet + "_" + item.port] | |
404 | else: | |
405 | s_id = self.createAndAddServiceToHost( | |
406 | h_id, | |
407 | item.service, | |
408 | item.protocol, | |
409 | ports=[str(item.port)] | |
410 | ) | |
411 | ids[item.subnet + "_" + item.port] = s_id | |
412 | if web: | |
413 | if item.severity not in self.ignored_severities: | |
414 | v_id = self.createAndAddVulnWebToService( | |
415 | h_id, | |
416 | s_id, | |
417 | item.name, | |
418 | desc=item.description, | |
419 | website=item.host, | |
420 | severity=item.severity, | |
421 | ref=ref, | |
422 | resolution=item.resolution, | |
423 | external_id=item.id) | |
424 | elif item.severity not in self.ignored_severities: | |
425 | self.createAndAddVulnToService( | |
426 | h_id, | |
427 | s_id, | |
428 | item.name, | |
429 | desc=item.description, | |
430 | severity=item.severity, | |
431 | ref=ref, | |
432 | resolution=item.resolution, | |
433 | external_id=item.id) | |
434 | del parser | |
435 | ||
436 | def _isIPV4(self, ip): | |
437 | if len(ip.split(".")) == 4: | |
438 | return True | |
439 | else: | |
440 | return False | |
441 | ||
442 | def processCommandString(self, username, current_path, command_string): | |
443 | return None | |
444 | ||
445 | def setHost(self): | |
446 | pass | |
447 | ||
448 | ||
449 | def createPlugin(): | |
450 | return OpenvasPlugin() | |
451 | ||
452 | if __name__ == "__main__": | |
453 | import sys | |
454 | import os | |
455 | if len(sys.argv) == 2: | |
456 | report_file = sys.argv[1] | |
457 | if os.path.isfile(report_file): | |
458 | plugin = createPlugin() | |
459 | plugin.processReport(report_file) | |
460 | print(plugin.get_json()) | |
461 | else: | |
462 | print(f"Report not found: {report_file}") | |
463 | else: | |
464 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
465 | ||
466 | # 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 | # 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 | # Author: @EzequielTBH | |
8 | from builtins import str | |
9 | ||
10 | from faraday_plugins.plugins.plugin import PluginBase | |
11 | import json | |
12 | import re | |
13 | ||
14 | __author__ = "@EzequielTBH" | |
15 | __copyright__ = "Copyright 2015, @EzequielTBH" | |
16 | __credits__ = "@EzequielTBH" | |
17 | __license__ = "GPL v3" | |
18 | __version__ = "1.0.0" | |
19 | ||
20 | ||
21 | class pasteAnalyzerPlugin(PluginBase): | |
22 | ||
23 | def __init__(self): | |
24 | super().__init__() | |
25 | self.id = "pasteAnalyzer" | |
26 | self.name = "pasteAnalyzer JSON Output Plugin" | |
27 | self.plugin_version = "1.0.0" | |
28 | self.command_string = "" | |
29 | self.current_path = "" | |
30 | self._command_regex = re.compile( | |
31 | r'^(pasteAnalyzer|python pasteAnalyzer.py|\./pasteAnalyzer.py|sudo python pasteAnalyzer.py|sudo \./pasteAnalyzer.py).*?') | |
32 | ||
33 | def parseOutputString(self, output, debug=False): | |
34 | ||
35 | print("[*]Parsing Output...") | |
36 | ||
37 | # Generating file name with full path. | |
38 | indexStart = self.command_string.find("-j") + 3 | |
39 | ||
40 | fileJson = self.command_string[ | |
41 | indexStart:self.command_string.find(" ", indexStart)] | |
42 | ||
43 | fileJson = self.current_path + "/" + fileJson | |
44 | ||
45 | try: | |
46 | with open(fileJson, "r") as fileJ: | |
47 | results = json.loads(fileJ.read()) | |
48 | ||
49 | except Exception as e: | |
50 | print("\n[!]Exception opening file\n" + str(e)) | |
51 | return | |
52 | ||
53 | if results == []: | |
54 | return | |
55 | ||
56 | print("[*]Results loaded...") | |
57 | ||
58 | # Configuration initial. | |
59 | hostId = self.createAndAddHost("pasteAnalyzer") | |
60 | interfaceId = self.createAndAddInterface(hostId, "Results") | |
61 | serviceId = self.createAndAddServiceToInterface( | |
62 | hostId, | |
63 | interfaceId, | |
64 | "Web", | |
65 | "TcpHTTP", | |
66 | ['80'] | |
67 | ) | |
68 | print("[*]Initial Configuration ready....") | |
69 | ||
70 | # Loading results. | |
71 | for i in range(0, len(results), 2): | |
72 | ||
73 | data = results[i + 1] | |
74 | description = "" | |
75 | ||
76 | for element in data: | |
77 | ||
78 | # Is Category | |
79 | if type(element) == str: #TODO bte arrray decode | |
80 | description += element + ": " | |
81 | ||
82 | # Is a list with results! | |
83 | else: | |
84 | for element2 in element: | |
85 | description += "\n" + element2 | |
86 | ||
87 | self.createAndAddVulnWebToService( | |
88 | hostId, | |
89 | serviceId, | |
90 | results[i], | |
91 | description | |
92 | ) | |
93 | ||
94 | print("[*]Parse finished, API faraday called...") | |
95 | ||
96 | def processCommandString(self, username, current_path, command_string): | |
97 | ||
98 | print("[*]pasteAnalyzer Plugin running...") | |
99 | ||
100 | if command_string.find("-j") < 0: | |
101 | command_string += " -j JSON_OUTPUT " | |
102 | ||
103 | self.command_string = command_string | |
104 | self.current_path = current_path | |
105 | ||
106 | return command_string | |
107 | ||
108 | ||
109 | def createPlugin(): | |
110 | return pasteAnalyzerPlugin() | |
111 | ||
112 | if __name__ == "__main__": | |
113 | import sys | |
114 | import os | |
115 | if len(sys.argv) == 2: | |
116 | report_file = sys.argv[1] | |
117 | if os.path.isfile(report_file): | |
118 | plugin = createPlugin() | |
119 | plugin.processReport(report_file) | |
120 | print(plugin.get_json()) | |
121 | else: | |
122 | print(f"Report not found: {report_file}") | |
123 | else: | |
124 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
125 | # 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 | # 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 socket | |
7 | from os import path | |
8 | from faraday_plugins.plugins.plugin import PluginBase | |
9 | from urllib.parse import urlparse | |
10 | ||
11 | __author__ = "Andres Tarantini" | |
12 | __copyright__ = "Copyright (c) 2015 Andres Tarantini" | |
13 | __credits__ = ["Andres Tarantini"] | |
14 | __license__ = "MIT" | |
15 | __version__ = "0.0.1" | |
16 | __maintainer__ = "Andres Tarantini" | |
17 | __email__ = "[email protected]" | |
18 | __status__ = "Development" | |
19 | ||
20 | ||
21 | class PeepingTomPlugin(PluginBase): | |
22 | """ | |
23 | Handle PeepingTom (https://bitbucket.org/LaNMaSteR53/peepingtom) output | |
24 | """ | |
25 | ||
26 | def __init__(self): | |
27 | super().__init__() | |
28 | self.id = "peepingtom" | |
29 | self.name = "PeepingTom" | |
30 | self.plugin_version = "0.0.1" | |
31 | self.version = "02.19.15" | |
32 | self._command_regex = re.compile( | |
33 | r'^(python peepingtom.py|\./peepingtom.py).*?') | |
34 | self._path = None | |
35 | ||
36 | def parseOutputString(self, output): | |
37 | # Find data path | |
38 | data_path_search = re.search(r"in '(.*)\/'", output) | |
39 | print(data_path_search) | |
40 | if not data_path_search: | |
41 | # No data path found | |
42 | return True | |
43 | ||
44 | # Parse "peepingtom.html" report and extract results | |
45 | data_path = data_path_search.groups()[0] | |
46 | html = open(path.join(self._path, data_path, "peepingtom.html")).read() | |
47 | for url in re.findall(r'href=[\'"]?([^\'" >]+)', html): | |
48 | if "://" in url: | |
49 | url_parsed = urlparse(url) | |
50 | address = socket.gethostbyname(url_parsed.netloc) | |
51 | host = self.createAndAddHost(address) | |
52 | iface = self.createAndAddInterface( | |
53 | host, address, ipv4_address=address) | |
54 | service = self.createAndAddServiceToInterface(host, iface, "http", protocol="tcp", ports=[80]) | |
55 | self.createAndAddNoteToService( | |
56 | host, | |
57 | service, | |
58 | 'screenshot', | |
59 | path.join( | |
60 | self._path, | |
61 | data_path_search.groups()[0], | |
62 | "{}.png".format(url.replace( | |
63 | "://", "").replace("/", "").replace(".", "")) | |
64 | ) | |
65 | ) | |
66 | ||
67 | return True | |
68 | ||
69 | def processCommandString(self, username, current_path, command_string): | |
70 | self._path = current_path | |
71 | ||
72 | ||
73 | def createPlugin(): | |
74 | return PeepingTomPlugin() | |
75 | ||
76 | if __name__ == "__main__": | |
77 | import sys | |
78 | import os | |
79 | if len(sys.argv) == 2: | |
80 | report_file = sys.argv[1] | |
81 | if os.path.isfile(report_file): | |
82 | plugin = createPlugin() | |
83 | plugin.processReport(report_file) | |
84 | print(plugin.get_json()) | |
85 | else: | |
86 | print(f"Report not found: {report_file}") | |
87 | else: | |
88 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
89 | ||
90 | # 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 | # 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 | ||
9 | __author__ = "Facundo de Guzmán, Esteban Guillardoy" | |
10 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
11 | __credits__ = ["Facundo de Guzmán", "Esteban Guillardoy"] | |
12 | __license__ = "" | |
13 | __version__ = "1.0.0" | |
14 | __maintainer__ = "Francisco Amato" | |
15 | __email__ = "[email protected]" | |
16 | __status__ = "Development" | |
17 | ||
18 | ||
19 | class CmdPingPlugin(PluginBase): | |
20 | """ | |
21 | This plugin handles ping command. | |
22 | Basically detects if user was able to connect to a device | |
23 | """ | |
24 | ||
25 | def __init__(self): | |
26 | super().__init__() | |
27 | self.id = "ping" | |
28 | self.name = "Ping" | |
29 | self.plugin_version = "0.0.1" | |
30 | self.version = "1.0.0" | |
31 | self._command_regex = re.compile( | |
32 | r'^(sudo ping|ping|sudo ping6|ping6).*?') | |
33 | ||
34 | def parseOutputString(self, output, debug=False): | |
35 | ||
36 | reg = re.search(r"PING ([\w\.-:]+)( |)\(([\w\.:]+)\)", output) | |
37 | if re.search("0 received|unknown host", output) is None and reg is not None: | |
38 | ||
39 | ip_address = reg.group(3) | |
40 | hostname = reg.group(1) | |
41 | ||
42 | h_id = self.createAndAddHost(ip_address) | |
43 | if self._isIPV4(ip_address): | |
44 | i_id = self.createAndAddInterface( | |
45 | h_id, ip_address, ipv4_address=ip_address, hostname_resolution=[hostname]) | |
46 | else: | |
47 | self.createAndAddInterface( | |
48 | h_id, ip_address, ipv6_address=ip_address, hostname_resolution=[hostname]) | |
49 | ||
50 | return True | |
51 | ||
52 | def _isIPV4(self, ip): | |
53 | if len(ip.split(".")) == 4: | |
54 | return True | |
55 | else: | |
56 | return False | |
57 | ||
58 | def processCommandString(self, username, current_path, command_string): | |
59 | """ | |
60 | """ | |
61 | return None | |
62 | ||
63 | ||
64 | def createPlugin(): | |
65 | return CmdPingPlugin() | |
66 | ||
67 | if __name__ == "__main__": | |
68 | import sys | |
69 | import os | |
70 | if len(sys.argv) == 2: | |
71 | report_file = sys.argv[1] | |
72 | if os.path.isfile(report_file): | |
73 | plugin = createPlugin() | |
74 | plugin.processReport(report_file) | |
75 | print(plugin.get_json()) | |
76 | else: | |
77 | print(f"Report not found: {report_file}") | |
78 | else: | |
79 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
80 | ||
81 | # 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 | # 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 | ||
9 | ||
10 | __author__ = "Federico Kirschbaum" | |
11 | __copyright__ = "Copyright 2011, Faraday Project" | |
12 | __credits__ = ["Federico Kirschbaum"] | |
13 | __license__ = "" | |
14 | __version__ = "1.0.0" | |
15 | __maintainer__ = "Federico Kirschbaum" | |
16 | __email__ = "[email protected]" | |
17 | __status__ = "Development" | |
18 | ||
19 | ||
20 | class CmdPropeciaPlugin(PluginBase): | |
21 | """ | |
22 | This plugin handles propecia command. | |
23 | Basically inserts into the tree the ouput of this tool | |
24 | """ | |
25 | ||
26 | def __init__(self): | |
27 | super().__init__() | |
28 | self.id = "propecia" | |
29 | self.name = "propecia port scanner" | |
30 | self.plugin_version = "0.0.1" | |
31 | self.version = "1.0" | |
32 | self.framework_version = "1.0.0" | |
33 | self.options = None | |
34 | self._current_output = None | |
35 | self._command_regex = re.compile( | |
36 | r'^(sudo propecia|\.\/propecia|propecia).*?') | |
37 | self._host_ip = None | |
38 | self._port = "23" | |
39 | ||
40 | def parseOutputString(self, output, debug=False): | |
41 | ||
42 | host_info = re.search( | |
43 | r"(\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b)", output) | |
44 | ||
45 | if host_info is None: | |
46 | self.logger.info("No hosts detected") | |
47 | else: | |
48 | for host in output.splitlines(): | |
49 | if host != "": | |
50 | h_id = self.createAndAddHost(host) | |
51 | i_id = self.createAndAddInterface( | |
52 | h_id, host, ipv4_address=host) | |
53 | s_id = self.createAndAddServiceToInterface(h_id, i_id, str(self._port), | |
54 | "tcp", | |
55 | ports=[self._port], | |
56 | status="open", | |
57 | version="", | |
58 | description="") | |
59 | if debug is True: | |
60 | self.logger.info("Debug is active") | |
61 | ||
62 | return True | |
63 | ||
64 | def processCommandString(self, username, current_path, command_string): | |
65 | """ | |
66 | """ | |
67 | count_args = command_string.split() | |
68 | ||
69 | if count_args.__len__() == 3: | |
70 | self._port = count_args[2] | |
71 | ||
72 | ||
73 | def createPlugin(): | |
74 | return CmdPropeciaPlugin() | |
75 | ||
76 | if __name__ == "__main__": | |
77 | import sys | |
78 | import os | |
79 | if len(sys.argv) == 2: | |
80 | report_file = sys.argv[1] | |
81 | if os.path.isfile(report_file): | |
82 | plugin = createPlugin() | |
83 | plugin.processReport(report_file) | |
84 | print(plugin.get_json()) | |
85 | else: | |
86 | print(f"Report not found: {report_file}") | |
87 | else: | |
88 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
89 | ||
90 | # 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 | # 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 os | |
7 | import logging | |
8 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
9 | ||
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 | |
18 | ||
19 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split('.')] | |
20 | ||
21 | logger = logging.getLogger(__name__) | |
22 | ||
23 | current_path = os.path.abspath(os.getcwd()) | |
24 | ||
25 | __author__ = 'Francisco Amato' | |
26 | __copyright__ = 'Copyright (c) 2013, Infobyte LLC' | |
27 | __credits__ = ['Francisco Amato'] | |
28 | __license__ = '' | |
29 | __version__ = '1.0.0' | |
30 | __maintainer__ = 'Francisco Amato' | |
31 | __email__ = '[email protected]' | |
32 | __status__ = 'Development' | |
33 | ||
34 | ||
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 | def cleaner_results(string): | |
43 | ||
44 | try: | |
45 | result = string.replace('<P>', '').replace('<UL>', ''). \ | |
46 | replace('<LI>', '').replace('<BR>', ''). \ | |
47 | replace('<A HREF="', '').replace('</A>', ' '). \ | |
48 | replace('" TARGET="_blank">', ' ').replace('"', '"') | |
49 | return result | |
50 | ||
51 | except: | |
52 | return '' | |
53 | ||
54 | ||
55 | class QualysguardXmlParser(): | |
56 | """ | |
57 | The objective of this class is to parse an xml file generated by | |
58 | the qualysguard tool. | |
59 | ||
60 | TODO: Handle errors. | |
61 | TODO: Test qualysguard output version. Handle what happens if the parser | |
62 | doesn't support it. | |
63 | TODO: Test cases. | |
64 | ||
65 | @param qualysguard_xml_filepath A proper xml generated by qualysguard | |
66 | """ | |
67 | ||
68 | def __init__(self, xml_output): | |
69 | tree, type_report = self.parse_xml(xml_output) | |
70 | ||
71 | if not tree or type_report is None: | |
72 | self.items = [] | |
73 | return | |
74 | ||
75 | if type_report == 'ASSET_DATA_REPORT': | |
76 | self.items = [data for data in self.get_items_asset_report(tree)] | |
77 | elif type_report == 'SCAN': | |
78 | self.items = [data for data in self.get_items_scan_report(tree)] | |
79 | ||
80 | def parse_xml(self, xml_output): | |
81 | """ | |
82 | Open and parse an xml file. | |
83 | ||
84 | TODO: Write custom parser to just read the nodes that we need instead | |
85 | of reading the whole file. | |
86 | ||
87 | @return xml_tree An xml tree instance. None if error. | |
88 | """ | |
89 | ||
90 | asset_data_report = '<!DOCTYPE ASSET_DATA_REPORT SYSTEM' | |
91 | scan_report = '<!DOCTYPE SCAN SYSTEM' | |
92 | ||
93 | try: | |
94 | tree = ET.fromstring(xml_output) | |
95 | ||
96 | if asset_data_report in xml_output: | |
97 | type_report = 'ASSET_DATA_REPORT' | |
98 | elif scan_report in xml_output: | |
99 | type_report = 'SCAN' | |
100 | else: | |
101 | type_report = None | |
102 | ||
103 | except SyntaxError as err: | |
104 | logger.error('SyntaxError: %s.' % (err)) | |
105 | return None, None | |
106 | ||
107 | return tree, type_report | |
108 | ||
109 | def get_items_scan_report(self, tree): | |
110 | """ | |
111 | @return items A list of Host instances | |
112 | """ | |
113 | for node in tree.findall('IP'): | |
114 | yield ItemScanReport(node) | |
115 | ||
116 | def get_items_asset_report(self, tree): | |
117 | """ | |
118 | @return items A list of Host instances | |
119 | """ | |
120 | for node in tree.find('HOST_LIST').findall('HOST'): | |
121 | yield ItemAssetReport(node, tree) | |
122 | ||
123 | ||
124 | class ItemAssetReport(): | |
125 | """ | |
126 | An abstract representation of a Item (HOST) for a Asset Report. | |
127 | @param item_node A item_node taken from an qualysguard xml tree | |
128 | """ | |
129 | ||
130 | def __init__(self, item_node, tree): | |
131 | ||
132 | self.node = item_node | |
133 | self.ip = self.get_text_from_subnode('IP') | |
134 | ||
135 | self.os = self.get_text_from_subnode('OPERATING_SYSTEM') | |
136 | self.vulns = self.getResults(tree) | |
137 | ||
138 | def getResults(self, tree): | |
139 | ||
140 | glossary = tree.find('GLOSSARY/VULN_DETAILS_LIST') | |
141 | ||
142 | for self.issue in self.node.find('VULN_INFO_LIST'): | |
143 | yield ResultsAssetReport(self.issue, glossary) | |
144 | ||
145 | def get_text_from_subnode(self, subnode_xpath_expr): | |
146 | """ | |
147 | Finds a subnode in the host node and the retrieves a value from it. | |
148 | ||
149 | @return An attribute value | |
150 | """ | |
151 | sub_node = self.node.find(subnode_xpath_expr) | |
152 | if sub_node is not None: | |
153 | return sub_node.text | |
154 | ||
155 | return None | |
156 | ||
157 | ||
158 | class ResultsAssetReport(): | |
159 | """ | |
160 | A abstraction of Results for a Asset Report of Qualysguard. | |
161 | """ | |
162 | ||
163 | def __init__(self, issue_node, glossary): | |
164 | ||
165 | # VULN_INFO ElementTree | |
166 | self.node = issue_node | |
167 | self.port = self.get_text_from_subnode(self.node, 'PORT') | |
168 | self.protocol = self.get_text_from_subnode(self.node, 'PROTOCOL') | |
169 | self.name = self.get_text_from_subnode(self.node, 'QID') | |
170 | self.result = self.get_text_from_subnode(self.node, 'RESULT') | |
171 | ||
172 | self.severity_dict = { | |
173 | '1': 'info', | |
174 | '2': 'info', | |
175 | '3': 'med', | |
176 | '4': 'high', | |
177 | '5': 'critical'} | |
178 | ||
179 | # GLOSSARY TAG | |
180 | self.glossary = glossary | |
181 | self.severity = self.severity_dict.get( | |
182 | self.get_text_from_glossary('SEVERITY'), 'info') | |
183 | self.title = self.get_text_from_glossary('TITLE') | |
184 | self.cvss = self.get_text_from_glossary('CVSS_SCORE/CVSS_BASE') | |
185 | self.pci = self.get_text_from_glossary('PCI_FLAG') | |
186 | self.solution = self.get_text_from_glossary('SOLUTION') | |
187 | self.impact = self.get_text_from_glossary('IMPACT') | |
188 | ||
189 | # Description | |
190 | self.desc = cleaner_results(self.get_text_from_glossary('THREAT')) | |
191 | if not self.desc: | |
192 | self.desc = '' | |
193 | if self.result: | |
194 | self.desc += '\n\nResult: ' + cleaner_results(self.result) | |
195 | if self.impact: | |
196 | self.desc += '\n\nImpact: ' + cleaner_results(self.impact) | |
197 | if self.result: | |
198 | self.desc += '\n\nSolution: ' + cleaner_results(self.solution) | |
199 | ||
200 | # References | |
201 | self.ref = [] | |
202 | self.ref.append(self.get_text_from_glossary('CVE_ID_LIST/CVE_ID/ID')) | |
203 | ||
204 | if self.cvss: | |
205 | self.ref.append('CVSS SCORE: ' + self.cvss) | |
206 | ||
207 | if self.pci: | |
208 | self.ref.append('PCI: ' + self.pci) | |
209 | ||
210 | def get_text_from_glossary(self, tag): | |
211 | """ | |
212 | Finds a subnode in the glossary and retrieves a value of this. | |
213 | Filter by QualysId. | |
214 | ||
215 | @return An attribute value | |
216 | """ | |
217 | ||
218 | for vuln_detail in self.glossary: | |
219 | ||
220 | id_act = vuln_detail.get('id').strip('qid_') | |
221 | if id_act == self.name: | |
222 | ||
223 | text = vuln_detail.find(tag) | |
224 | if text is not None: | |
225 | return cleaner_unicode(text.text) | |
226 | else: | |
227 | return None | |
228 | ||
229 | def get_text_from_subnode(self, node, subnode_xpath_expr): | |
230 | """ | |
231 | Finds a subnode in the node and the retrieves a value from it. | |
232 | ||
233 | @return An attribute value | |
234 | """ | |
235 | sub_node = node.find(subnode_xpath_expr) | |
236 | if sub_node is not None: | |
237 | return cleaner_unicode(sub_node.text) | |
238 | ||
239 | return None | |
240 | ||
241 | ||
242 | class ItemScanReport(): | |
243 | """ | |
244 | An abstract representation of a Item for a 'SCAN' report of Qualysguard. | |
245 | ||
246 | @param item_node A item_node taken from an qualysguard xml tree | |
247 | """ | |
248 | ||
249 | def __init__(self, item_node): | |
250 | self.node = item_node | |
251 | self.ip = item_node.get('value') | |
252 | self.os = self.get_text_from_subnode('OS') | |
253 | self.hostname = self.get_hostname(item_node) | |
254 | self.vulns = self.getResults(item_node) | |
255 | ||
256 | def getResults(self, tree): | |
257 | """ | |
258 | :param tree: | |
259 | """ | |
260 | for self.issues in tree.findall('VULNS/CAT'): | |
261 | for v in self.issues.findall('VULN'): | |
262 | yield ResultsScanReport(v, self.issues) | |
263 | for self.issues in tree.findall('INFOS/CAT'): | |
264 | for v in self.issues.findall('INFO'): | |
265 | yield ResultsScanReport(v, self.issues) | |
266 | for self.issues in tree.findall('SERVICES/CAT'): | |
267 | for v in self.issues.findall('SERVICE'): | |
268 | yield ResultsScanReport(v, self.issues) | |
269 | for self.issues in tree.findall('PRACTICES/CAT'): | |
270 | for v in self.issues.findall('PRACTICE'): | |
271 | yield ResultsScanReport(v, self.issues) | |
272 | ||
273 | def get_text_from_subnode(self, subnode_xpath_expr): | |
274 | """ | |
275 | Finds a subnode in the host node and the retrieves a value from it. | |
276 | ||
277 | @return An attribute value | |
278 | """ | |
279 | sub_node = self.node.find(subnode_xpath_expr) | |
280 | if sub_node is not None: | |
281 | return sub_node.text | |
282 | ||
283 | return None | |
284 | ||
285 | def get_hostname(self, node): | |
286 | hostname = node.get('name') | |
287 | ||
288 | if hostname == 'No registered hostname': | |
289 | return "" | |
290 | ||
291 | return hostname | |
292 | ||
293 | ||
294 | class ResultsScanReport(): | |
295 | """ | |
296 | An abstraction of Result for Qualysguard 'SCAN' Report. | |
297 | """ | |
298 | ||
299 | def __init__(self, issue_node, parent): | |
300 | self.node = issue_node | |
301 | self.port = parent.get('port') | |
302 | self.protocol = parent.get('protocol') | |
303 | self.name = self.node.get('number') | |
304 | self.external_id = self.node.get('number') | |
305 | self.severity = self.node.get('severity') | |
306 | self.title = self.get_text_from_subnode('TITLE') | |
307 | self.cvss = self.get_text_from_subnode('CVSS_BASE') | |
308 | self.diagnosis = self.get_text_from_subnode('DIAGNOSIS') | |
309 | self.solution = self.get_text_from_subnode('SOLUTION') | |
310 | self.result = self.get_text_from_subnode('RESULT') | |
311 | self.consequence = self.get_text_from_subnode('CONSEQUENCE') | |
312 | ||
313 | self.desc = cleaner_results(self.diagnosis) | |
314 | if self.result: | |
315 | self.desc += '\nResult: ' + cleaner_results(self.result) | |
316 | else: | |
317 | self.desc += '' | |
318 | ||
319 | if self.consequence: | |
320 | self.desc += '\nConsequence: ' + cleaner_results(self.consequence) | |
321 | else: | |
322 | self.desc += '' | |
323 | ||
324 | self.ref = [] | |
325 | for r in issue_node.findall('CVE_ID_LIST/CVE_ID'): | |
326 | self.node = r | |
327 | self.ref.append(self.get_text_from_subnode('ID')) | |
328 | for r in issue_node.findall('BUGTRAQ_ID_LIST/BUGTRAQ_ID'): | |
329 | self.node = r | |
330 | self.ref.append('bid-' + self.get_text_from_subnode('ID')) | |
331 | ||
332 | if self.cvss: | |
333 | self.ref.append('CVSS BASE: ' + self.cvss) | |
334 | ||
335 | def get_text_from_subnode(self, subnode_xpath_expr): | |
336 | """ | |
337 | Finds a subnode in the host node and the retrieves a value from it. | |
338 | ||
339 | @return An attribute value | |
340 | """ | |
341 | sub_node = self.node.find(subnode_xpath_expr) | |
342 | if sub_node is not None: | |
343 | return cleaner_results(cleaner_unicode(sub_node.text)) | |
344 | ||
345 | return None | |
346 | ||
347 | ||
348 | class QualysguardPlugin(PluginXMLFormat): | |
349 | """ | |
350 | Example plugin to parse qualysguard output. | |
351 | """ | |
352 | ||
353 | def __init__(self): | |
354 | super().__init__() | |
355 | self.identifier_tag = ["ASSET_DATA_REPORT", "SCAN"] | |
356 | self.id = 'Qualysguard' | |
357 | self.name = 'Qualysguard XML Output Plugin' | |
358 | self.plugin_version = '0.0.2' | |
359 | self.version = 'Qualysguard 8.17.1.0.2' | |
360 | self.framework_version = '1.0.0' | |
361 | self.options = None | |
362 | self._current_output = None | |
363 | self._command_regex = re.compile( | |
364 | r'^(sudo qualysguard|\.\/qualysguard).*?') | |
365 | self.open_options = {"mode": "r", "encoding": "utf-8"} | |
366 | ||
367 | def parseOutputString(self, output, debug=False): | |
368 | ||
369 | parser = QualysguardXmlParser(output) | |
370 | ||
371 | for item in parser.items: | |
372 | h_id = self.createAndAddHost( | |
373 | item.ip, | |
374 | item.os, | |
375 | hostnames=[item.hostname]) | |
376 | ||
377 | for v in item.vulns: | |
378 | if v.port is None: | |
379 | self.createAndAddVulnToHost( | |
380 | h_id, | |
381 | v.title if v.title else v.name, | |
382 | ref=v.ref, | |
383 | severity=str(int(v.severity) - 1), | |
384 | resolution=v.solution if v.solution else '', | |
385 | desc=v.desc, | |
386 | external_id=v.external_id) | |
387 | ||
388 | else: | |
389 | ||
390 | web = False | |
391 | s_id = self.createAndAddServiceToHost( | |
392 | h_id, | |
393 | v.port, | |
394 | v.protocol, | |
395 | ports=[str(v.port)], | |
396 | status='open') | |
397 | ||
398 | if v.port in ['80', '443'] or re.search('ssl|http', v.name): | |
399 | web = True | |
400 | else: | |
401 | web = False | |
402 | ||
403 | if web: | |
404 | self.createAndAddVulnWebToService( | |
405 | h_id, | |
406 | s_id, | |
407 | v.title if v.title else v.name, | |
408 | ref=v.ref, | |
409 | website=item.ip, | |
410 | severity=str(int(v.severity) - 1), | |
411 | desc=v.desc, | |
412 | resolution=v.solution if v.solution else '', | |
413 | external_id=v.external_id) | |
414 | ||
415 | else: | |
416 | self.createAndAddVulnToService( | |
417 | h_id, | |
418 | s_id, | |
419 | v.title if v.title else v.name, | |
420 | ref=v.ref, | |
421 | severity=str(int(v.severity) - 1), | |
422 | desc=v.desc, | |
423 | resolution=v.solution if v.solution else '', | |
424 | external_id=v.external_id) | |
425 | ||
426 | del parser | |
427 | ||
428 | def processCommandString(self, username, current_path, command_string): | |
429 | return None | |
430 | ||
431 | def setHost(self): | |
432 | pass | |
433 | ||
434 | ||
435 | def createPlugin(): | |
436 | return QualysguardPlugin() | |
437 | ||
438 | ||
439 | if __name__ == "__main__": | |
440 | import sys | |
441 | import os | |
442 | if len(sys.argv) == 2: | |
443 | report_file = sys.argv[1] | |
444 | if os.path.isfile(report_file): | |
445 | plugin = createPlugin() | |
446 | plugin.processReport(report_file) | |
447 | print(plugin.get_json()) | |
448 | else: | |
449 | print(f"Report not found: {report_file}") | |
450 | else: | |
451 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
452 | # I'm Py3 |
0 | # I'm Py3⏎ |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2017 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | """ | |
5 | import re | |
6 | import json | |
7 | import socket | |
8 | import logging | |
9 | try: | |
10 | from lxml import etree as ET | |
11 | except ImportError: | |
12 | import xml.etree.ElementTree as ET | |
13 | ||
14 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
15 | ||
16 | __author__ = 'Leonardo Lazzaro' | |
17 | __copyright__ = 'Copyright (c) 2017, Infobyte LLC' | |
18 | __credits__ = ['Leonardo Lazzaro'] | |
19 | __license__ = '' | |
20 | __version__ = '0.1.0' | |
21 | __maintainer__ = 'Leonardo Lazzaro' | |
22 | __email__ = '[email protected]' | |
23 | __status__ = 'Development' | |
24 | ||
25 | logger = logging.getLogger(__name__) | |
26 | ||
27 | ||
28 | class ReconngParser: | |
29 | def __init__(self, output): | |
30 | self._format = self.report_format(output) | |
31 | self.hosts = [] | |
32 | self.vulns = [] | |
33 | ||
34 | if self._format == 'xml': | |
35 | self.parsable_tree = self.get_parseable_xml_output(output) | |
36 | self.parse_xml_report(self.parsable_tree) | |
37 | ||
38 | elif self._format == 'json': | |
39 | self.parse_json_report(output) | |
40 | ||
41 | def report_format(self, output): | |
42 | xml_format_regex = re.compile(r'^<(.*?)>') | |
43 | json_format_regex = re.compile(r'(^{)') | |
44 | ||
45 | if xml_format_regex.match(output): | |
46 | output_format = 'xml' | |
47 | elif json_format_regex.match(output): | |
48 | output_format = 'json' | |
49 | else: | |
50 | return False | |
51 | ||
52 | return output_format | |
53 | ||
54 | def get_parseable_xml_output(self, xml_output): | |
55 | try: | |
56 | tree = ET.fromstring(xml_output) | |
57 | return tree | |
58 | except IndexError: | |
59 | print("Syntax error") | |
60 | return None | |
61 | ||
62 | def parse_xml_report(self, tree): | |
63 | hosts_items = tree.xpath('//hosts/item') | |
64 | self.hosts_from_report(hosts_items) | |
65 | ||
66 | vulnerabilities_items = tree.xpath('//vulnerabilities/item') | |
67 | self.vulns_from_report(vulnerabilities_items) | |
68 | ||
69 | def parse_json_report(self, output): | |
70 | reconng_data = json.loads(output) | |
71 | hosts_items = reconng_data.get('hosts', '') | |
72 | self.hosts_from_report(hosts_items) | |
73 | ||
74 | vulns_items = reconng_data.get('vulnerabilities','') | |
75 | self.vulns_from_report(vulns_items) | |
76 | ||
77 | def hosts_from_report(self, hosts_items): | |
78 | for host in hosts_items: | |
79 | host_info = self.get_info_from_host_element(host) | |
80 | self.hosts.append(host_info) | |
81 | ||
82 | def vulns_from_report(self, vulns_items): | |
83 | for vuln in vulns_items: | |
84 | vuln_info = self.get_info_from_vuln_element(vuln) | |
85 | self.vulns.append(vuln_info) | |
86 | ||
87 | def get_info_from_host_element(self, element): | |
88 | info = {} | |
89 | if self._format == 'xml': | |
90 | info['host'] = element.find('host').text | |
91 | info['ip'] = element.find('ip_address').text | |
92 | ||
93 | elif self._format == 'json': | |
94 | info['host'] = element['host'] | |
95 | info['ip'] = element['ip_address'] | |
96 | ||
97 | return info | |
98 | ||
99 | def get_info_from_vuln_element(self, element): | |
100 | info = {} | |
101 | if self._format == 'xml': | |
102 | info['host'] = element.find('host').text | |
103 | info['reference'] = element.find('reference').text | |
104 | info['module'] = element.find('module').text | |
105 | info['example'] = element.find('example').text | |
106 | info['category'] = element.find('category').text | |
107 | elif self._format == 'json': | |
108 | info['category'] = element['category'] | |
109 | info['host'] = element['host'] | |
110 | info['module'] = element['module'] | |
111 | info['reference'] = element['reference'] | |
112 | info['example'] = element['example'] | |
113 | ||
114 | if 'XSS' in info['category']: | |
115 | info['severity'] = 'high' | |
116 | elif 'SSL' in info['category']: | |
117 | info['severity'] = 'med' | |
118 | else: | |
119 | info['severity'] = 'info' | |
120 | ||
121 | return info | |
122 | ||
123 | ||
124 | class ReconngPlugin(PluginXMLFormat): | |
125 | """ | |
126 | Example plugin to parse qualysguard output. | |
127 | """ | |
128 | ||
129 | def __init__(self): | |
130 | super().__init__() | |
131 | self.identifier_tag = "reconng" | |
132 | self.id = 'Reconng' | |
133 | self.name = 'Reconng XML Output Plugin' | |
134 | self.plugin_version = '0.0.3' | |
135 | self.version = '' | |
136 | self.framework_version = '' | |
137 | self.options = None | |
138 | self._current_output = None | |
139 | self._command_regex = re.compile( | |
140 | r'records added to') | |
141 | ||
142 | self.host_mapper = {} | |
143 | ||
144 | def parseOutputString(self, output): | |
145 | parser = ReconngParser(output) | |
146 | ||
147 | for host in parser.hosts: | |
148 | h_id = self.createAndAddHost( | |
149 | host['ip'], | |
150 | hostnames=[host['host']] | |
151 | ) | |
152 | self.host_mapper[host['host']] = h_id | |
153 | for vuln in parser.vulns: | |
154 | if vuln['host'] not in list(self.host_mapper.keys()): | |
155 | ip = self.resolve_host(vuln['host']) | |
156 | h_id = self.createAndAddHost( | |
157 | ip, | |
158 | hostnames=[vuln['host']] | |
159 | ) | |
160 | self.host_mapper[vuln['host']] = h_id | |
161 | else: | |
162 | h_id = self.host_mapper[vuln['host']] | |
163 | ||
164 | self.createAndAddVulnToHost( | |
165 | name='Recon-ng found: ' + vuln['category'] + ' vulnerability', | |
166 | desc='Found by module: ' + vuln['module'], | |
167 | severity=vuln['severity'], | |
168 | ref=[vuln['reference']], | |
169 | host_id=h_id, | |
170 | data=vuln['example'] | |
171 | ) | |
172 | ||
173 | def processCommandString(self, username, current_path, command_string): | |
174 | return | |
175 | ||
176 | def resolve_host(self, host): | |
177 | try: | |
178 | return socket.gethostbyname(host) | |
179 | except Exception: | |
180 | pass | |
181 | return host | |
182 | ||
183 | ||
184 | def createPlugin(): | |
185 | return ReconngPlugin() | |
186 | ||
187 | ||
188 | if __name__ == "__main__": | |
189 | import sys | |
190 | import os | |
191 | if len(sys.argv) == 2: | |
192 | report_file = sys.argv[1] | |
193 | if os.path.isfile(report_file): | |
194 | plugin = createPlugin() | |
195 | plugin.processReport(report_file) | |
196 | print(plugin.get_json()) | |
197 | else: | |
198 | print(f"Report not found: {report_file}") | |
199 | else: | |
200 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
201 | # 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 | # 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 | import re | |
7 | import os | |
8 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
9 | ||
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 | |
18 | ||
19 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] | |
20 | ||
21 | current_path = os.path.abspath(os.getcwd()) | |
22 | ||
23 | __author__ = "Francisco Amato" | |
24 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
25 | __credits__ = ["Francisco Amato"] | |
26 | __license__ = "" | |
27 | __version__ = "1.0.0" | |
28 | __maintainer__ = "Francisco Amato" | |
29 | __email__ = "[email protected]" | |
30 | __status__ = "Development" | |
31 | ||
32 | ||
33 | class RetinaXmlParser: | |
34 | """ | |
35 | The objective of this class is to parse an xml file generated by the retina tool. | |
36 | ||
37 | TODO: Handle errors. | |
38 | TODO: Test retina output version. Handle what happens if the parser doesn't support it. | |
39 | TODO: Test cases. | |
40 | ||
41 | @param retina_xml_filepath A proper xml generated by retina | |
42 | """ | |
43 | ||
44 | def __init__(self, xml_output): | |
45 | tree = self.parse_xml(xml_output) | |
46 | if tree: | |
47 | self.items = [data for data in self.get_items(tree)] | |
48 | else: | |
49 | self.items = [] | |
50 | ||
51 | def parse_xml(self, xml_output): | |
52 | """ | |
53 | Open and parse an xml file. | |
54 | ||
55 | TODO: Write custom parser to just read the nodes that we need instead of | |
56 | reading the whole file. | |
57 | ||
58 | @return xml_tree An xml tree instance. None if error. | |
59 | """ | |
60 | try: | |
61 | tree = ET.fromstring(xml_output) | |
62 | except SyntaxError as err: | |
63 | print("SyntaxError: %s. %s" % (err, xml_output)) | |
64 | return None | |
65 | ||
66 | return tree | |
67 | ||
68 | def get_items(self, tree): | |
69 | """ | |
70 | @return items A list of Host instances | |
71 | """ | |
72 | for node in tree.findall("hosts/host"): | |
73 | yield Item(node) | |
74 | ||
75 | ||
76 | class Item: | |
77 | """ | |
78 | An abstract representation of a Item | |
79 | ||
80 | ||
81 | @param item_node A item_node taken from an retina xml tree | |
82 | """ | |
83 | ||
84 | def __init__(self, item_node): | |
85 | self.node = item_node | |
86 | self.ip = self.get_text_from_subnode("ip") | |
87 | self.hostname = "" if self.get_text_from_subnode( | |
88 | "dnsName") == "unknown" else self.get_text_from_subnode("dnsName") | |
89 | self.netbiosname = self.get_text_from_subnode("netBIOSName") | |
90 | self.netbiosdomain = self.get_text_from_subnode("netBIOSDomain") | |
91 | self.os = self.get_text_from_subnode("os") | |
92 | self.mac = self.get_text_from_subnode("mac") | |
93 | ||
94 | self.vulns = self.getResults(item_node) | |
95 | self.ports = {} | |
96 | for v in self.vulns: | |
97 | if v.port not in self.ports: | |
98 | self.ports[v.port] = [] | |
99 | self.ports[v.port].append(v) | |
100 | ||
101 | def getResults(self, tree): | |
102 | """ | |
103 | :param tree: | |
104 | """ | |
105 | for self.issues in tree.findall("audit"): | |
106 | yield Results(self.issues) | |
107 | ||
108 | def get_text_from_subnode(self, subnode_xpath_expr): | |
109 | """ | |
110 | Finds a subnode in the host node and the retrieves a value from it. | |
111 | ||
112 | @return An attribute value | |
113 | """ | |
114 | sub_node = self.node.find(subnode_xpath_expr) | |
115 | if sub_node is not None: | |
116 | return sub_node.text | |
117 | ||
118 | return None | |
119 | ||
120 | ||
121 | class Results(): | |
122 | ||
123 | def __init__(self, issue_node): | |
124 | self.node = issue_node | |
125 | self.name = self.get_text_from_subnode('name') | |
126 | ||
127 | self.description = self.get_text_from_subnode('description') | |
128 | self.solution = self.get_text_from_subnode('fixInformation') | |
129 | self.severity = self.get_text_from_subnode('risk') | |
130 | self.cve = "" if self.get_text_from_subnode( | |
131 | 'cve') == 'N/A' else self.get_text_from_subnode('cve') | |
132 | self.cce = self.get_text_from_subnode('cce') | |
133 | self.date = self.get_text_from_subnode('date') | |
134 | self.pciLevel = self.get_text_from_subnode('pciLevel') | |
135 | self.pciReason = self.get_text_from_subnode('pciReason') | |
136 | self.pciPassFail = self.get_text_from_subnode('pciPassFail') | |
137 | self.cvssScore = self.get_text_from_subnode('cvssScore') | |
138 | self.exploit = self.get_text_from_subnode('exploit') | |
139 | self.context = self.get_text_from_subnode('context') | |
140 | val = self.context.split(":") | |
141 | self.port = "" | |
142 | self.protocol = "" | |
143 | if len(val) == 2: | |
144 | if val[0] in ['TCP', 'UDP']: | |
145 | self.protocol = val[0] | |
146 | self.port = val[1] | |
147 | ||
148 | self.desc = self.get_text_from_subnode('description') | |
149 | self.solution = self.solution if self.solution else "" | |
150 | self.desc += "\nExploit: " + self.exploit if self.exploit else "" | |
151 | self.desc += "\ncvssScore: " + self.cvssScore if self.cvssScore else "" | |
152 | self.desc += "\nContext: " + self.context if self.context else "" | |
153 | ||
154 | self.ref = [] | |
155 | if self.cve: | |
156 | self.ref = self.cve.split(",") | |
157 | ||
158 | def get_text_from_subnode(self, subnode_xpath_expr): | |
159 | """ | |
160 | Finds a subnode in the host node and the retrieves a value from it. | |
161 | ||
162 | @return An attribute value | |
163 | """ | |
164 | sub_node = self.node.find(subnode_xpath_expr) | |
165 | if sub_node is not None: | |
166 | return sub_node.text | |
167 | ||
168 | return None | |
169 | ||
170 | ||
171 | class RetinaPlugin(PluginXMLFormat): | |
172 | """ | |
173 | Example plugin to parse retina output. | |
174 | """ | |
175 | ||
176 | def __init__(self): | |
177 | super().__init__() | |
178 | self.identifier_tag = "scanJob" | |
179 | self.id = "Retina" | |
180 | self.name = "Retina XML Output Plugin" | |
181 | self.plugin_version = "0.0.1" | |
182 | self.version = "Retina Network 5.19.2.2718" | |
183 | self.framework_version = "1.0.0" | |
184 | self.options = None | |
185 | self._current_output = None | |
186 | self._command_regex = re.compile(r'^(sudo retina|\.\/retina).*?') | |
187 | ||
188 | ||
189 | def parseOutputString(self, output, debug=False): | |
190 | ||
191 | parser = RetinaXmlParser(output) | |
192 | for item in parser.items: | |
193 | h_id = self.createAndAddHost(item.ip, item.os) | |
194 | hostname = item.hostname if item.hostname else item.ip | |
195 | i_id = self.createAndAddInterface( | |
196 | h_id, item.ip, ipv4_address=item.ip, hostname_resolution=[hostname]) | |
197 | ||
198 | if not item.netbiosname == 'N/A': | |
199 | self.createAndAddNoteToHost( | |
200 | h_id, "netBIOSName", item.netbiosname) | |
201 | ||
202 | if not item.netbiosdomain == 'N/A': | |
203 | self.createAndAddNoteToHost( | |
204 | h_id, "netBIOSDomain", item.netbiosdomain) | |
205 | ||
206 | for k, vulns in item.ports.items(): | |
207 | if k: | |
208 | for v in vulns: | |
209 | web = False | |
210 | s_id = self.createAndAddServiceToInterface(h_id, i_id, 'unknown', | |
211 | v.protocol.lower(), | |
212 | ports=[str(v.port)], | |
213 | status="open") | |
214 | ||
215 | if v.port in ['80', '443'] or re.search("ssl|http", v.name.lower()): | |
216 | web = True | |
217 | else: | |
218 | web = False | |
219 | ||
220 | 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")) | |
223 | 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")) | |
226 | else: | |
227 | 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")) | |
230 | del parser | |
231 | ||
232 | def processCommandString(self, username, current_path, command_string): | |
233 | return None | |
234 | ||
235 | def setHost(self): | |
236 | pass | |
237 | ||
238 | ||
239 | def createPlugin(): | |
240 | return RetinaPlugin() | |
241 | ||
242 | if __name__ == "__main__": | |
243 | import sys | |
244 | import os | |
245 | if len(sys.argv) == 2: | |
246 | report_file = sys.argv[1] | |
247 | if os.path.isfile(report_file): | |
248 | plugin = createPlugin() | |
249 | plugin.processReport(report_file) | |
250 | print(plugin.get_json()) | |
251 | else: | |
252 | print(f"Report not found: {report_file}") | |
253 | else: | |
254 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
255 | ||
256 | # 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 | # 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 | ||
10 | ||
11 | ||
12 | current_path = os.path.abspath(os.getcwd()) | |
13 | ||
14 | __author__ = "Francisco Amato" | |
15 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
16 | __credits__ = ["Francisco Amato"] | |
17 | __license__ = "" | |
18 | __version__ = "1.0.0" | |
19 | __maintainer__ = "Francisco Amato" | |
20 | __email__ = "[email protected]" | |
21 | __status__ = "Development" | |
22 | ||
23 | ||
24 | class ReverseraiderParser: | |
25 | """ | |
26 | The objective of this class is to parse an xml file generated by the reverseraider tool. | |
27 | ||
28 | @param reverseraider_filepath A proper simple report generated by reverseraider | |
29 | """ | |
30 | ||
31 | def __init__(self, output): | |
32 | ||
33 | lists = output.split("\r\n") | |
34 | self.items = [] | |
35 | ||
36 | if re.search("ReverseRaider domain scanner|Error opening", output) is not None: | |
37 | return | |
38 | ||
39 | for line in lists: | |
40 | if line != "": | |
41 | print("(%s)" % line) | |
42 | info = line.split("\t") | |
43 | if info.__len__() > 0: | |
44 | item = {'host': info[0], 'ip': info[1]} | |
45 | print("host = %s, ip = %s" % (info[0], info[1])) | |
46 | self.items.append(item) | |
47 | ||
48 | ||
49 | class ReverseraiderPlugin(PluginBase): | |
50 | """ | |
51 | Example plugin to parse reverseraider output. | |
52 | """ | |
53 | ||
54 | def __init__(self): | |
55 | super().__init__() | |
56 | self.id = "Reverseraider" | |
57 | self.name = "Reverseraider XML Output Plugin" | |
58 | self.plugin_version = "0.0.1" | |
59 | self.version = "0.7.6" | |
60 | self.options = None | |
61 | self._current_output = None | |
62 | self._current_path = None | |
63 | self._command_regex = re.compile( | |
64 | r'^(sudo \.\/reverseraider|\.\/reverseraider).*?') | |
65 | self._completition = { | |
66 | "": "reverseraider -d domain | -r range [options]", | |
67 | "-r": "range of ipv4 or ipv6 addresses, for reverse scanning", | |
68 | "-d": "domain, for wordlist scanning (example google.com)", | |
69 | "-w": "wordlist file (see wordlists directory...)", | |
70 | "-t": "requests timeout in seconds", | |
71 | "-P": "enable numeric permutation on wordlist (default off)", | |
72 | "-D": "nameserver to use (default: resolv.conf)", | |
73 | "-T": "use TCP queries instead of UDP queries", | |
74 | "-R": "don't set the recursion bit on queries", | |
75 | } | |
76 | ||
77 | global current_path | |
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): | |
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 | ||
105 | del parser | |
106 | ||
107 | def processCommandString(self, username, current_path, command_string): | |
108 | """ | |
109 | """ | |
110 | return None | |
111 | ||
112 | ||
113 | def createPlugin(): | |
114 | return ReverseraiderPlugin() | |
115 | ||
116 | ||
117 | if __name__ == "__main__": | |
118 | import sys | |
119 | import os | |
120 | if len(sys.argv) == 2: | |
121 | report_file = sys.argv[1] | |
122 | if os.path.isfile(report_file): | |
123 | plugin = createPlugin() | |
124 | plugin.processReport(report_file) | |
125 | print(plugin.get_json()) | |
126 | else: | |
127 | print(f"Report not found: {report_file}") | |
128 | else: | |
129 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
130 | # 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 | # 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 | import re | |
7 | import os | |
8 | import json | |
9 | import socket | |
10 | import random | |
11 | from faraday_plugins.plugins.plugin import PluginBase | |
12 | ||
13 | current_path = os.path.abspath(os.getcwd()) | |
14 | ||
15 | __author__ = "Nicolas Rodriguez" | |
16 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
17 | __credits__ = ["Nicolas Rodriguez"] | |
18 | __license__ = "" | |
19 | __version__ = "1.0.0" | |
20 | __maintainer__ = "Francisco Amato" | |
21 | __email__ = "[email protected]" | |
22 | __status__ = "Development" | |
23 | ||
24 | ||
25 | class SkipfishParser: | |
26 | """ | |
27 | The objective of this class is to parse an xml file generated by | |
28 | the skipfish tool. | |
29 | ||
30 | TODO: Handle errors. | |
31 | TODO: Test skipfish output version. Handle what happens if the parser | |
32 | doesn't support it. | |
33 | TODO: Test cases. | |
34 | ||
35 | @param skipfish_filepath A proper xml generated by skipfish | |
36 | """ | |
37 | ||
38 | def __init__(self, skipfish_filepath): | |
39 | self.filepath = skipfish_filepath | |
40 | ||
41 | tmp = open(skipfish_filepath + "/samples.js", "r").read() | |
42 | data = self.extract_data( | |
43 | tmp, | |
44 | "var issue_samples =", "];", | |
45 | lambda x: x.replace("'", '"'), | |
46 | False, | |
47 | False) | |
48 | # Escape characters not allowed in JSON, repr fix this with double Escape | |
49 | # Also remove \n character and space for have a valid JSON. | |
50 | issues = json.loads(repr(data[1]).replace("\\n"," ").replace("'","") + "]") | |
51 | ||
52 | tmp = open(skipfish_filepath + "/index.html", "r").read() | |
53 | err_msg = json.loads( | |
54 | self.extract_data( | |
55 | tmp, | |
56 | "var issue_desc=", | |
57 | "};", | |
58 | lambda x: self.convert_quotes(x, "'", '"'), | |
59 | False, | |
60 | False) | |
61 | [1] + "}") | |
62 | ||
63 | self.err_msg = err_msg | |
64 | self.issues = issues | |
65 | ||
66 | def convert_quotes(self, text, quote="'", inside='"'): | |
67 | start = 0 | |
68 | while True: | |
69 | pos = text.find(quote, start) | |
70 | ||
71 | if pos == -1: | |
72 | break | |
73 | ||
74 | ss = text[:pos - 1] | |
75 | quotes = len(ss) - len(ss.replace(inside, "")) | |
76 | ||
77 | if quotes % 2 == 0: | |
78 | text = text[:pos - 1] + "\\" + quote + text[pos + 1:] | |
79 | ||
80 | start = pos + 1 | |
81 | return text | |
82 | ||
83 | def extract_data(self, samples, start_tag, end_tag, fn=lambda x: x, include_start_tag=True, include_end_tag=True): | |
84 | start = samples.find(start_tag) | |
85 | ||
86 | if start == -1: | |
87 | return (-1, None) | |
88 | ||
89 | end = samples.find(end_tag, start + 1) | |
90 | ||
91 | if end == -1: | |
92 | return (-2, None) | |
93 | ||
94 | data = samples[start:end + len(end_tag)] | |
95 | data = fn(data) | |
96 | ||
97 | if not include_start_tag: | |
98 | data = data[len(start_tag) + 1:] | |
99 | ||
100 | if not include_end_tag: | |
101 | data = data[:-1 * len(end_tag)] | |
102 | ||
103 | return (0, data) | |
104 | ||
105 | ||
106 | class SkipfishPlugin(PluginBase): | |
107 | """ | |
108 | Example plugin to parse skipfish output. | |
109 | """ | |
110 | ||
111 | def __init__(self): | |
112 | super().__init__() | |
113 | self.id = "Skipfish" | |
114 | self.name = "Skipfish XML Output Plugin" | |
115 | self.plugin_version = "0.0.2" | |
116 | self.version = "2.1.5" | |
117 | self.options = None | |
118 | self._current_output = None | |
119 | self.parent = None | |
120 | 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): | |
125 | """ | |
126 | This method will discard the output the shell sends, it will read it | |
127 | from the xml where it expects it to be present. | |
128 | ||
129 | NOTE: if 'debug' is true then it is being run from a test case and the | |
130 | output being sent is valid. | |
131 | """ | |
132 | ||
133 | if not os.path.exists(self._output_path): | |
134 | return False | |
135 | ||
136 | p = SkipfishParser(self._output_path) | |
137 | ||
138 | hostc = {} | |
139 | port = 80 | |
140 | for issue in p.issues: | |
141 | req = "" | |
142 | res = "" | |
143 | for sample in issue["samples"]: | |
144 | if not sample["url"] in hostc: | |
145 | reg = re.search( | |
146 | "(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\.\,\?\'\\\+&%\$#\=~_\-]+)).*?$", sample["url"]) | |
147 | ||
148 | protocol = reg.group(1) | |
149 | host = reg.group(4) | |
150 | if reg.group(11) is not None: | |
151 | port = reg.group(11) | |
152 | else: | |
153 | port = 443 if protocol == "https" else 80 | |
154 | ||
155 | ip = self.resolve(host) | |
156 | ||
157 | h_id = self.createAndAddHost(ip) | |
158 | i_id = self.createAndAddInterface( | |
159 | h_id, | |
160 | ip, | |
161 | ipv4_address=ip, | |
162 | hostname_resolution=[host]) | |
163 | ||
164 | s_id = self.createAndAddServiceToInterface( | |
165 | h_id, | |
166 | i_id, | |
167 | "http", | |
168 | "tcp", | |
169 | ports=[port], | |
170 | status="open") | |
171 | ||
172 | hostc[sample["url"]] = { | |
173 | 'h_id': h_id, | |
174 | 'ip': ip, | |
175 | 'port': port, | |
176 | 'host': host, | |
177 | 'protocol': protocol, | |
178 | 'i_id': i_id, | |
179 | 's_id': s_id} | |
180 | ||
181 | try: | |
182 | req = open("%s/request.dat" % sample["dir"], "r").read() | |
183 | except: | |
184 | pass | |
185 | ||
186 | try: | |
187 | res = open("%s/request.dat" % sample["dir"], "r").read() | |
188 | except Exception: | |
189 | pass | |
190 | ||
191 | d = hostc[sample["url"]] | |
192 | self.createAndAddVulnWebToService( | |
193 | d['h_id'], | |
194 | d['s_id'], | |
195 | name=p.err_msg[str(issue["type"])], | |
196 | desc="Extra: " + sample["extra"], | |
197 | website=d['host'], | |
198 | path=sample["url"], | |
199 | severity=issue["severity"]) | |
200 | ||
201 | def resolve(self, host): | |
202 | try: | |
203 | return socket.gethostbyname(host) | |
204 | except Exception: | |
205 | pass | |
206 | return host | |
207 | ||
208 | xml_arg_re = re.compile(r"^.*(-o\s*[^\s]+).*$") | |
209 | ||
210 | def processCommandString(self, username, current_path, command_string): | |
211 | """ | |
212 | Adds the -o parameter to get report of the command string that the | |
213 | user has set. | |
214 | """ | |
215 | 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 | ||
221 | 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) | |
227 | else: | |
228 | return re.sub( | |
229 | arg_match.group(1), | |
230 | r"-o %s" % self._output_path, | |
231 | command_string, | |
232 | 1) | |
233 | ||
234 | def setHost(self): | |
235 | pass | |
236 | ||
237 | ||
238 | def createPlugin(): | |
239 | return SkipfishPlugin() | |
240 | ||
241 | if __name__ == "__main__": | |
242 | import sys | |
243 | import os | |
244 | if len(sys.argv) == 2: | |
245 | report_file = sys.argv[1] | |
246 | if os.path.isfile(report_file): | |
247 | plugin = createPlugin() | |
248 | plugin.processReport(report_file) | |
249 | print(plugin.get_json()) | |
250 | else: | |
251 | print(f"Report not found: {report_file}") | |
252 | else: | |
253 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
254 | ||
255 | ||
256 | # 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 | # 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 | from faraday_plugins.plugins.plugin import PluginBase | |
6 | import re | |
7 | ||
8 | __author__ = "Andres Tarantini" | |
9 | __copyright__ = "Copyright (c) 2015 Andres Tarantini" | |
10 | __credits__ = ["Andres Tarantini"] | |
11 | __license__ = "MIT" | |
12 | __version__ = "0.0.1" | |
13 | __maintainer__ = "Andres Tarantini" | |
14 | __email__ = "[email protected]" | |
15 | __status__ = "Development" | |
16 | ||
17 | ||
18 | class SSHDefaultScanPlugin(PluginBase): | |
19 | """ | |
20 | Handle sshdefaultscan (https://github.com/atarantini/sshdefaultscan) output | |
21 | using --batch and --batch-template; supports --username and --password | |
22 | """ | |
23 | ||
24 | def __init__(self): | |
25 | super().__init__() | |
26 | self.id = "sshdefaultscan" | |
27 | self.name = "sshdefaultscan" | |
28 | self.plugin_version = "0.0.1" | |
29 | self.version = "1.0.0" | |
30 | self._command_regex = re.compile( | |
31 | r'^(python sshdefaultscan.py|\./sshdefaultscan.py).*?') | |
32 | self._completition = {"--fast": "Fast scan mode"} | |
33 | ||
34 | def parseOutputString(self, output, debug=False): | |
35 | for line in [l.strip() for l in output.split("\n")]: | |
36 | output_rexeg_match = re.match( | |
37 | r".*:.*@\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", line) | |
38 | if output_rexeg_match: | |
39 | credentials, address = line.split("@") | |
40 | host = self.createAndAddHost(address) | |
41 | iface = self.createAndAddInterface( | |
42 | host, address, ipv4_address=address) | |
43 | service = self.createAndAddServiceToInterface( | |
44 | host, iface, "ssh", protocol="tcp", ports=[22] | |
45 | ) | |
46 | username, password = credentials.split(":") | |
47 | cred = self.createAndAddCredToService( | |
48 | host, service, username, password) | |
49 | vuln = self.createAndAddVulnToService( | |
50 | host, | |
51 | service, | |
52 | "Default credentials", | |
53 | desc="The SSH server have default credentials ({username}:{password})".format( | |
54 | username=username, | |
55 | password=password | |
56 | ), | |
57 | severity=3 | |
58 | ) | |
59 | ||
60 | return True | |
61 | ||
62 | def processCommandString(self, username, current_path, command_string): | |
63 | if "--batch" not in command_string: | |
64 | return "{command} --batch --batch-template {template}".format( | |
65 | command=command_string, | |
66 | template="{username}:{password}@{host}" | |
67 | ) | |
68 | ||
69 | return None | |
70 | ||
71 | ||
72 | def createPlugin(): | |
73 | return SSHDefaultScanPlugin() | |
74 | ||
75 | if __name__ == "__main__": | |
76 | import sys | |
77 | import os | |
78 | if len(sys.argv) == 2: | |
79 | report_file = sys.argv[1] | |
80 | if os.path.isfile(report_file): | |
81 | plugin = createPlugin() | |
82 | plugin.processReport(report_file) | |
83 | print(plugin.get_json()) | |
84 | else: | |
85 | print(f"Report not found: {report_file}") | |
86 | else: | |
87 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
88 | ||
89 | # I'm Py3 |
0 | # I'm Py3⏎ |
0 | import re | |
1 | import os | |
2 | import random | |
3 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
4 | ||
5 | try: | |
6 | from lxml import etree as ET | |
7 | except ImportError: | |
8 | import xml.etree.ElementTree as ET | |
9 | ||
10 | ||
11 | WEAK_CIPHER_LIST = [ | |
12 | "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", | |
13 | "TLS_RSA_WITH_AES_128_CBC_SHA", | |
14 | "TLS_RSA_WITH_AES_128_CBC_SHA256", | |
15 | "TLS_RSA_WITH_AES_128_GCM_SHA256", | |
16 | "TLS_RSA_WITH_AES_256_CBC_SHA", | |
17 | "TLS_RSA_WITH_AES_256_CBC_SHA256", | |
18 | "TLS_RSA_WITH_AES_256_GCM_SHA384", | |
19 | "TLS_RSA_WITH_3DES_EDE_CBC_SHA", | |
20 | "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", | |
21 | "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA" | |
22 | ] | |
23 | ||
24 | ||
25 | class SslyzeXmlParser: | |
26 | ||
27 | def __init__(self, xml_output): | |
28 | self.parser = self.parse_xml(xml_output) | |
29 | self.target = self.get_target(self.parser) | |
30 | self.certificate = self.get_hostname_validation(self.parser) | |
31 | self.cipher_suite = self.get_weak_cipher_suite(self.parser) | |
32 | self.heart_bleed = self.get_heartbleed(self.parser) | |
33 | self.open_ssl_ccs = self.get_openssl_ccs(self.parser) | |
34 | ||
35 | def parse_xml(self, xml_output): | |
36 | try: | |
37 | tree = ET.fromstring(xml_output) | |
38 | return tree | |
39 | except IndexError: | |
40 | print("Syntax error") | |
41 | return None | |
42 | ||
43 | def get_target(self, tree): | |
44 | return tree.xpath('//target') | |
45 | ||
46 | def get_hostname_validation(self, tree): | |
47 | return tree.xpath('//hostnameValidation') | |
48 | ||
49 | def get_protocol_name(self, tree): | |
50 | protocol_supported = [] | |
51 | protocols = [] | |
52 | protocols.append(tree.xpath('//sslv2')) | |
53 | protocols.append(tree.xpath('//sslv3')) | |
54 | protocols.append(tree.xpath('//tlsv1')) | |
55 | protocols.append(tree.xpath('//tlsv1_1')) | |
56 | protocols.append(tree.xpath('//tlsv1_2')) | |
57 | protocols.append(tree.xpath('//tlsv1_3')) | |
58 | ||
59 | for protocol in protocols: | |
60 | if protocol[0].attrib['isProtocolSupported'] == "True": | |
61 | protocol_supported.append(protocol[0]) | |
62 | ||
63 | return protocol_supported | |
64 | ||
65 | def get_weak_cipher_suite(self, tree): | |
66 | protocols = self.get_protocol_name(tree) | |
67 | weak_cipher = {} | |
68 | ||
69 | for protocol in protocols: | |
70 | weak_cipher[protocol.tag] = [] | |
71 | for ciphers in protocol: | |
72 | if ciphers.tag == 'preferredCipherSuite' or ciphers.tag == 'acceptedCipherSuites': | |
73 | for cipher in ciphers: | |
74 | if cipher.attrib['name'] in WEAK_CIPHER_LIST: | |
75 | if not cipher.attrib['name'] in weak_cipher[protocol.tag]: | |
76 | weak_cipher[protocol.tag].append(cipher.attrib['name']) | |
77 | ||
78 | return weak_cipher | |
79 | ||
80 | def get_heartbleed(self, tree): | |
81 | return tree.xpath('//heartbleed') | |
82 | ||
83 | def get_openssl_ccs(self, tree): | |
84 | return tree.xpath('//openssl_ccs') | |
85 | ||
86 | ||
87 | class SslyzePlugin(PluginXMLFormat): | |
88 | ||
89 | def __init__(self): | |
90 | super().__init__() | |
91 | self.identifier_tag = "document" | |
92 | self.id = "Sslyze" | |
93 | self.name = "Sslyze Plugin" | |
94 | self.plugin_version = "0.0.1" | |
95 | self.version = "2.0.6" | |
96 | self.framework_version = "1.0.0" | |
97 | self.options = None | |
98 | self._current_output = None | |
99 | self._command_regex = re.compile(r'^(sudo sslyze|sslyze|\.\/sslyze).*?') | |
100 | self.xml_arg_re = re.compile(r"^.*(--xml_output\s*[^\s]+).*$") | |
101 | ||
102 | def report_belongs_to(self, **kwargs): | |
103 | if super().report_belongs_to(**kwargs): | |
104 | report_path = kwargs.get("report_path", "") | |
105 | with open(report_path) as f: | |
106 | output = f.read() | |
107 | return re.search("SSLyzeVersion", output) is not None | |
108 | return False | |
109 | ||
110 | def parseOutputString(self, output, debug=False): | |
111 | parser = SslyzeXmlParser(output) | |
112 | host = parser.target[0].attrib['host'] | |
113 | ip = parser.target[0].attrib['ip'] | |
114 | port = parser.target[0].attrib['port'] | |
115 | protocol = parser.target[0].attrib['tlsWrappedProtocol'] | |
116 | cipher = parser.cipher_suite | |
117 | ||
118 | # Creating host | |
119 | host_id = self.createAndAddHost(ip) | |
120 | # Creating service CHANGE NAME | |
121 | service_id = self.createAndAddServiceToHost( | |
122 | host_id, | |
123 | name=protocol, | |
124 | protocol=protocol, | |
125 | ports=[port], | |
126 | ) | |
127 | ||
128 | # Checking if certificate matches | |
129 | certificate = parser.certificate[0].attrib['certificateMatchesServerHostname'] | |
130 | server_hostname = parser.certificate[0].attrib['serverHostname'] | |
131 | if certificate.lower() == 'false': | |
132 | self.createAndAddVulnToService( | |
133 | host_id, | |
134 | service_id, | |
135 | name="Certificate mismatch", | |
136 | desc="Certificate does not match server hostname {}".format(server_hostname), | |
137 | severity="info") | |
138 | #Ciphers | |
139 | cipher = parser.cipher_suite | |
140 | ||
141 | for key in cipher: | |
142 | for value in cipher[key]: | |
143 | self.createAndAddVulnToService( | |
144 | host_id, | |
145 | service_id, | |
146 | name=value, | |
147 | desc="In protocol [{}], weak cipher suite: {}".format(key, value), | |
148 | severity="low") | |
149 | ||
150 | #Heartbleed | |
151 | heartbleed = parser.heart_bleed | |
152 | ||
153 | if heartbleed[0][0].attrib['isVulnerable'].lower() == 'true': | |
154 | self.createAndAddVulnToService( | |
155 | host_id, | |
156 | service_id, | |
157 | name="OpenSSL Heartbleed", | |
158 | desc="OpenSSL Heartbleed is vulnerable", | |
159 | severity="critical") | |
160 | ||
161 | #OpenSsl CCS Injection | |
162 | openssl_ccs = parser.open_ssl_ccs | |
163 | ||
164 | if openssl_ccs[0][0].attrib['isVulnerable'].lower() == 'true': | |
165 | self.createAndAddVulnToService( | |
166 | host_id, | |
167 | service_id, | |
168 | name="OpenSSL CCS Injection", | |
169 | desc="OpenSSL CCS Injection is vulnerable", | |
170 | severity="medium") | |
171 | ||
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 | ||
181 | arg_match = self.xml_arg_re.match(command_string) | |
182 | ||
183 | if arg_match is None: | |
184 | return re.sub(r"(^.*?sslyze)", | |
185 | r"\1 --xml_out %s" % self._output_file_path, | |
186 | command_string) | |
187 | else: | |
188 | return re.sub(arg_match.group(1), | |
189 | r"--xml_out %s" % self._output_file_path, | |
190 | command_string) | |
191 | ||
192 | ||
193 | def createPlugin(): | |
194 | return SslyzePlugin() | |
195 | ||
196 | if __name__ == "__main__": | |
197 | import sys | |
198 | import os | |
199 | if len(sys.argv) == 2: | |
200 | report_file = sys.argv[1] | |
201 | if os.path.isfile(report_file): | |
202 | plugin = createPlugin() | |
203 | plugin.processReport(report_file) | |
204 | print(plugin.get_json()) | |
205 | else: | |
206 | print(f"Report not found: {report_file}") | |
207 | else: | |
208 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
209 | ||
210 | # 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 | # 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 | current_path = os.path.abspath(os.getcwd()) | |
12 | ||
13 | __author__ = "Facundo de Guzmán, Esteban Guillardoy" | |
14 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
15 | __credits__ = ["Facundo de Guzmán", "Esteban Guillardoy"] | |
16 | __license__ = "" | |
17 | __version__ = "1.0.0" | |
18 | __maintainer__ = "Facundo de Guzmán" | |
19 | __email__ = "[email protected]" | |
20 | __status__ = "Development" | |
21 | ||
22 | ||
23 | class TelnetRouterPlugin(PluginBase): | |
24 | """ | |
25 | This plugin handles telnet command. | |
26 | Basically detects if user was able to connect to a device | |
27 | """ | |
28 | ||
29 | def __init__(self): | |
30 | super().__init__() | |
31 | self.id = "Telnet" | |
32 | self.name = "Telnet" | |
33 | self.plugin_version = "0.0.1" | |
34 | self.version = "0.17" | |
35 | self.framework_version = "1.0.0" | |
36 | self.options = None | |
37 | self._current_output = None | |
38 | self._command_regex = re.compile(r'^telnet.*?') | |
39 | self._host_ip = None | |
40 | self._host = [] | |
41 | self._port = "23" | |
42 | self._completition = { | |
43 | "": "telnet [-468ELadr] [-S tos] [-b address] [-e escapechar] [-l user] [-n tracefile] [host [port]]", | |
44 | "-4": "Force IPv4 address resolution.", | |
45 | "-6": "Force IPv6 address resolution.", | |
46 | "-8": "Request 8-bit operation. This causes an attempt to negotiate the TELNET BINARY option for both input and output. By default telnet is not 8-bit clean.", | |
47 | "-E": "Disables the escape character functionality; that is, sets the escape character to ``no character''.", | |
48 | "-L": "Specifies an 8-bit data path on output. This causes the TELNET BINARY option to be negotiated on just output.", | |
49 | "-a": "Attempt automatic login. Currently, this sends the user name via the USER variable of the ENVIRON option if supported by the remote system. The username is retrieved via getlogin(3).", | |
50 | "-b": "-b <address> Use bind(2) on the local socket to bind it to a specific local address.", | |
51 | "-d": "Sets the initial value of the debug toggle to TRUE.", | |
52 | "-r": "Emulate rlogin(1). In this mode, the default escape character is a tilde. Also, the interpretation of the escape character is changed: an escape character followed by a dot causes telnet to disconnect from the remote host. A ^Z instead of a dot suspends telnet, and a ^] (the default telnet escape character) generates a normal telnet prompt. These codes are accepted only at the beginning of a line.", | |
53 | "-S": "-S <tos> Sets the IP type-of-service (TOS) option for the telnet connection to the value tos.", | |
54 | "-e": "-e <escapechar> Sets the escape character to escapechar. If no character is supplied, no escape character will be used. Entering the escape character while connected causes telnet to drop to command mode.", | |
55 | "-l": "-l <user> Specify user as the user to log in as on the remote system. This is accomplished by sending the specified name as the USER environment variable, so it requires that the remote system support the TELNET ENVIRON option. This option implies the -a option, and may also be used with the open command.", | |
56 | "-n": "-n <tracefile> Opens tracefile for recording trace information. See the set tracefile command below.", | |
57 | } | |
58 | ||
59 | global current_path | |
60 | ||
61 | def resolve(self, host): | |
62 | try: | |
63 | return socket.gethostbyname(host) | |
64 | except: | |
65 | pass | |
66 | return host | |
67 | ||
68 | def parseOutputString(self, output, debug=False): | |
69 | ||
70 | host_info = re.search(r"Connected to (.+)\.", output) | |
71 | ||
72 | hostname = host_info.group(1) | |
73 | ip_address = self.resolve(hostname) | |
74 | ||
75 | if host_info is not None: | |
76 | h_id = self.createAndAddHost(ip_address) | |
77 | i_id = self.createAndAddInterface( | |
78 | h_id, ip_address, ipv4_address=ip_address, hostname_resolution=[hostname]) | |
79 | s_id = self.createAndAddServiceToInterface(h_id, i_id, self._port, | |
80 | "tcp", | |
81 | ports=[self._port], | |
82 | status="open") | |
83 | return True | |
84 | ||
85 | def processCommandString(self, username, current_path, command_string): | |
86 | ||
87 | count_args = command_string.split() | |
88 | ||
89 | c = count_args.__len__() | |
90 | self._port = "23" | |
91 | if re.search(r"[\d]+", count_args[c - 1]): | |
92 | self._port = count_args[c - 1] | |
93 | ||
94 | ||
95 | def createPlugin(): | |
96 | return TelnetRouterPlugin() | |
97 | ||
98 | if __name__ == "__main__": | |
99 | import sys | |
100 | import os | |
101 | if len(sys.argv) == 2: | |
102 | report_file = sys.argv[1] | |
103 | if os.path.isfile(report_file): | |
104 | plugin = createPlugin() | |
105 | plugin.processReport(report_file) | |
106 | print(plugin.get_json()) | |
107 | else: | |
108 | print(f"Report not found: {report_file}") | |
109 | else: | |
110 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
111 | # 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 | # 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 | ||
10 | ||
11 | ||
12 | current_path = os.path.abspath(os.getcwd()) | |
13 | ||
14 | __author__ = "Francisco Amato" | |
15 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
16 | __credits__ = ["Francisco Amato"] | |
17 | __license__ = "" | |
18 | __version__ = "1.0.0" | |
19 | __maintainer__ = "Francisco Amato" | |
20 | __email__ = "[email protected]" | |
21 | __status__ = "Development" | |
22 | ||
23 | ||
24 | class TheharvesterParser: | |
25 | """ | |
26 | The objective of this class is to parse an xml file generated by the theharvester tool. | |
27 | ||
28 | TODO: Handle errors. | |
29 | TODO: Test theharvester output version. Handle what happens if the parser doesn't support it. | |
30 | TODO: Test cases. | |
31 | ||
32 | @param theharvester_filepath A proper simple report generated by theharvester | |
33 | """ | |
34 | ||
35 | def __init__(self, output): | |
36 | ||
37 | self.items = [] | |
38 | _hosts, _vhosts = [], [] | |
39 | ||
40 | mregex = re.search( | |
41 | "\[\+\] Hosts found in search engines:[-=\s]+([\w\W]*)\[\+\]", output) | |
42 | if mregex is None: | |
43 | mregex = re.search( | |
44 | "\[\+\] Hosts found in search engines:[-=\s]+([\w\W]*)\n", output) | |
45 | mregex2 = re.search("\[\+\] Virtual hosts:[-=\s]+([\w\W]*)\n", output) | |
46 | ||
47 | if mregex is None and mregex2 is None: | |
48 | return | |
49 | ||
50 | if mregex: | |
51 | _hosts = mregex.group(1).strip().split("\n") | |
52 | if mregex2: | |
53 | _vhosts = mregex2.group(1).strip().split("\n") | |
54 | ||
55 | for line in _hosts: | |
56 | ||
57 | info = line.split(":") | |
58 | ||
59 | if len(info) > 1: | |
60 | item = {'host': info[1].strip(), 'ip': info[0].strip()} | |
61 | ||
62 | self.items.append(item) | |
63 | ||
64 | for line in _vhosts: | |
65 | ||
66 | info = line.split() | |
67 | if len(info) > 1: | |
68 | item = {'host': info[1].strip(), 'ip': info[0].strip()} | |
69 | ||
70 | self.items.append(item) | |
71 | ||
72 | ||
73 | class TheharvesterPlugin(PluginBase): | |
74 | """ | |
75 | Example plugin to parse theharvester output. | |
76 | """ | |
77 | ||
78 | def __init__(self): | |
79 | super().__init__() | |
80 | self.id = "Theharvester" | |
81 | self.name = "Theharvester XML Output Plugin" | |
82 | self.plugin_version = "0.0.1" | |
83 | self.version = "2.2a" | |
84 | self.options = None | |
85 | self._current_output = None | |
86 | self._current_path = None | |
87 | self._command_regex = re.compile( | |
88 | r'^(theharvester|sudo theharvester|sudo theHarvester\.py|theHarvester\.py|python theHarvester\.py|\.\/theHarvester\.py).*?') | |
89 | self._completition = { | |
90 | "": "Examples:./theharvester.py -d microsoft.com -l 500 -b google", | |
91 | "-d": "Domain to search or company name", | |
92 | "-b": "Data source (google,bing,bingapi,pgp,linkedin,google-profiles,exalead,all)", | |
93 | "-s": "Start in result number X (default 0)", | |
94 | "-v": "Verify host name via dns resolution and search for vhosts(basic)", | |
95 | "-l": "Limit the number of results to work with(bing goes from 50 to 50 results,", | |
96 | "-f": "Save the results into an XML file", | |
97 | "-n": "Perform a DNS reverse query on all ranges discovered", | |
98 | "-c": "Perform a DNS brute force for the domain name", | |
99 | "-t": "Perform a DNS TLD expansion discovery", | |
100 | "-e": "Use this DNS server", | |
101 | "-h": "use SHODAN database to query discovered hosts. google 100 to 100, and pgp doesn't use this option)", | |
102 | } | |
103 | ||
104 | global current_path | |
105 | ||
106 | def parseOutputString(self, output, debug=False): | |
107 | """ | |
108 | This method will discard the output the shell sends, it will read it from | |
109 | the xml where it expects it to be present. | |
110 | ||
111 | NOTE: if 'debug' is true then it is being run from a test case and the | |
112 | output being sent is valid. | |
113 | """ | |
114 | ||
115 | print("este es el output (%s)" % output) | |
116 | ||
117 | if debug: | |
118 | parser = TheharvesterParser(output) | |
119 | else: | |
120 | ||
121 | parser = TheharvesterParser(output) | |
122 | ||
123 | print(len(parser.items)) | |
124 | for item in parser.items: | |
125 | host = [] | |
126 | if item['host'] != item['ip']: | |
127 | host = [item['host']] | |
128 | h_id = self.createAndAddHost(item['ip']) | |
129 | i_id = self.createAndAddInterface(h_id, item['ip'], ipv4_address=item[ | |
130 | 'ip'], hostname_resolution=host) | |
131 | ||
132 | del parser | |
133 | ||
134 | def processCommandString(self, username, current_path, command_string): | |
135 | """ | |
136 | """ | |
137 | return None | |
138 | ||
139 | ||
140 | def createPlugin(): | |
141 | return TheharvesterPlugin() | |
142 | ||
143 | if __name__ == "__main__": | |
144 | import sys | |
145 | import os | |
146 | if len(sys.argv) == 2: | |
147 | report_file = sys.argv[1] | |
148 | if os.path.isfile(report_file): | |
149 | plugin = createPlugin() | |
150 | plugin.processReport(report_file) | |
151 | print(plugin.get_json()) | |
152 | else: | |
153 | print(f"Report not found: {report_file}") | |
154 | else: | |
155 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
156 | ||
157 | ||
158 | # 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 | # 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 | import re | |
7 | from faraday_plugins.plugins.plugin import PluginBase | |
8 | ||
9 | __author__ = "Ezequiel Tavella - @EzequielTBH" | |
10 | __copyright__ = "Copyright 2015, @EzequielTBH" | |
11 | __credits__ = "Ezequiel Tavella - @EzequielTBH" | |
12 | __license__ = "GPL v3" | |
13 | __version__ = "1.0.0" | |
14 | ||
15 | ||
16 | class traceroutePlugin(PluginBase): | |
17 | ||
18 | def __init__(self): | |
19 | super().__init__() | |
20 | self.id = "Traceroute" | |
21 | self.name = "Traceroute" | |
22 | self.plugin_version = "1.0.0" | |
23 | self.command_string = "" | |
24 | self._command_regex = re.compile( | |
25 | r'^(traceroute|traceroute6).*?') | |
26 | ||
27 | def parseOutputString(self, output, debug=False): | |
28 | ||
29 | print("[*]Parsing Output...") | |
30 | ||
31 | # Check no results. | |
32 | if not output.startswith("traceroute to"): | |
33 | return | |
34 | ||
35 | # Check if last parameter is host or ( packetlen or data size). | |
36 | parameters = self.command_string.split(' ') | |
37 | parameters.reverse() | |
38 | hostName = parameters[0] | |
39 | ||
40 | try: | |
41 | int(hostName) | |
42 | # No exception => host is the next item. | |
43 | hostName = parameters[1] | |
44 | except: | |
45 | pass | |
46 | ||
47 | # Add host and note with output of traceroute. | |
48 | hostId = self.createAndAddHost(hostName) | |
49 | self.createAndAddNoteToHost(hostId, "Traceroute Results", output) | |
50 | ||
51 | print("[*]Parse finished, API faraday called...") | |
52 | ||
53 | def processCommandString(self, username, current_path, command_string): | |
54 | ||
55 | print("[*]traceroute Plugin running...") | |
56 | self.command_string = command_string | |
57 | return command_string | |
58 | ||
59 | ||
60 | def createPlugin(): | |
61 | return traceroutePlugin() | |
62 | ||
63 | ||
64 | if __name__ == "__main__": | |
65 | import sys | |
66 | import os | |
67 | if len(sys.argv) == 2: | |
68 | report_file = sys.argv[1] | |
69 | if os.path.isfile(report_file): | |
70 | plugin = createPlugin() | |
71 | plugin.processReport(report_file) | |
72 | print(plugin.get_json()) | |
73 | else: | |
74 | print(f"Report not found: {report_file}") | |
75 | else: | |
76 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
77 | # 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 | # 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 PluginXMLFormat | |
8 | import re | |
9 | import os | |
10 | import socket | |
11 | import pprint | |
12 | import sys | |
13 | ||
14 | try: | |
15 | import xml.etree.cElementTree as ET | |
16 | import xml.etree.ElementTree as ET_ORIG | |
17 | ETREE_VERSION = ET_ORIG.VERSION | |
18 | except ImportError: | |
19 | import xml.etree.ElementTree as ET | |
20 | ETREE_VERSION = ET.VERSION | |
21 | ||
22 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] | |
23 | ||
24 | current_path = os.path.abspath(os.getcwd()) | |
25 | ||
26 | __author__ = "Francisco Amato" | |
27 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
28 | __credits__ = ["Francisco Amato"] | |
29 | __license__ = "" | |
30 | __version__ = "1.0.0" | |
31 | __maintainer__ = "Francisco Amato" | |
32 | __email__ = "[email protected]" | |
33 | __status__ = "Development" | |
34 | ||
35 | ||
36 | class W3afXmlParser: | |
37 | """ | |
38 | The objective of this class is to parse an xml file generated by the w3af tool. | |
39 | ||
40 | TODO: Handle errors. | |
41 | TODO: Test w3af output version. Handle what happens if the parser doesn't support it. | |
42 | TODO: Test cases. | |
43 | ||
44 | @param w3af_xml_filepath A proper xml generated by w3af | |
45 | """ | |
46 | ||
47 | def __init__(self, xml_output): | |
48 | self.target = None | |
49 | self.port = "80" | |
50 | self.host = None | |
51 | ||
52 | tree = self.parse_xml(xml_output) | |
53 | ||
54 | if tree: | |
55 | self.items = [data for data in self.get_items(tree)] | |
56 | else: | |
57 | self.items = [] | |
58 | ||
59 | def parse_xml(self, xml_output): | |
60 | """ | |
61 | Open and parse an xml file. | |
62 | ||
63 | TODO: Write custom parser to just read the nodes that we need instead of | |
64 | reading the whole file. | |
65 | ||
66 | @return xml_tree An xml tree instance. None if error. | |
67 | """ | |
68 | try: | |
69 | tree = ET.fromstring(xml_output) | |
70 | except SyntaxError as err: | |
71 | print("SyntaxError: %s. %s" % (err, xml_output)) | |
72 | return None | |
73 | ||
74 | return tree | |
75 | ||
76 | def get_items(self, tree): | |
77 | """ | |
78 | @return items A list of Host instances | |
79 | """ | |
80 | bugtype = "" | |
81 | ||
82 | if len(tree.findall('scan-info')) == 0: | |
83 | scaninfo = tree.findall('scaninfo')[0] | |
84 | else: | |
85 | scaninfo = tree.findall('scan-info')[0] | |
86 | ||
87 | 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) | |
97 | ||
98 | for node in tree.findall('vulnerability'): | |
99 | yield Item(node) | |
100 | for node in tree.findall('information'): | |
101 | yield Item(node) | |
102 | ||
103 | ||
104 | def get_attrib_from_subnode(xml_node, subnode_xpath_expr, attrib_name): | |
105 | """ | |
106 | Finds a subnode in the item node and the retrieves a value from it | |
107 | ||
108 | @return An attribute value | |
109 | """ | |
110 | global ETREE_VERSION | |
111 | node = None | |
112 | ||
113 | if ETREE_VERSION[0] <= 1 and ETREE_VERSION[1] < 3: | |
114 | ||
115 | match_obj = re.search( | |
116 | "([^\@]+?)\[\@([^=]*?)=\'([^\']*?)\'", subnode_xpath_expr) | |
117 | if match_obj is not None: | |
118 | node_to_find = match_obj.group(1) | |
119 | xpath_attrib = match_obj.group(2) | |
120 | xpath_value = match_obj.group(3) | |
121 | for node_found in xml_node.findall(node_to_find): | |
122 | if node_found.attrib[xpath_attrib] == xpath_value: | |
123 | node = node_found | |
124 | break | |
125 | else: | |
126 | node = xml_node.find(subnode_xpath_expr) | |
127 | ||
128 | else: | |
129 | node = xml_node.find(subnode_xpath_expr) | |
130 | ||
131 | if node is not None: | |
132 | return node.get(attrib_name) | |
133 | ||
134 | return None | |
135 | ||
136 | ||
137 | class Item: | |
138 | """ | |
139 | An abstract representation of a Item | |
140 | ||
141 | ||
142 | @param item_node A item_node taken from an w3af xml tree | |
143 | """ | |
144 | ||
145 | def __init__(self, item_node): | |
146 | self.node = item_node | |
147 | ||
148 | self.id = self.node.get('id') | |
149 | self.name = self.node.get('name') | |
150 | self.url = self.node.get('url') | |
151 | self.url = self.url if self.url != 'None' else "/" | |
152 | self.plugin = self.node.get('plugin') | |
153 | self.detail = self.get_text_from_subnode('description') | |
154 | self.resolution = self.get_text_from_subnode('fix-guidance') | |
155 | self.fix_effort = self.get_text_from_subnode('fix-effort') | |
156 | self.longdetail = self.get_text_from_subnode('description') | |
157 | self.severity = self.node.get('severity') | |
158 | self.method = self.node.get('method') | |
159 | self.ref = [] | |
160 | self.param = self.node.get('var') if self.node.get( | |
161 | 'var') != "None" else "" | |
162 | for ref in self.node.findall('references/reference'): | |
163 | self.ref.append(ref.get('url')) | |
164 | ||
165 | self.req = self.resp = '' | |
166 | for tx in self.node.findall('http-transactions/http-transaction'): | |
167 | if tx.find('http-request'): | |
168 | hreq = tx.find('http-request') | |
169 | else: | |
170 | hreq = tx.find('httprequest') | |
171 | ||
172 | if tx.find('http-response'): | |
173 | hres = tx.find('http-response') | |
174 | else: | |
175 | hres = tx.find('httpresponse') | |
176 | ||
177 | self.req = hreq.find('status').text | |
178 | for h in hreq.findall('headers/header'): | |
179 | self.req += "\n%s: %s" % (h.get('field'), h.get('content')) | |
180 | ||
181 | self.resp = hres.find('status').text | |
182 | for h in hres.findall('headers/header'): | |
183 | self.resp += "\n%s: %s" % (h.get('field'), h.get('content')) | |
184 | ||
185 | if hres.find('body'): | |
186 | self.resp += "\n%s" % hres.find('body').text | |
187 | ||
188 | def do_clean(self, value): | |
189 | myreturn = "" | |
190 | if value is not None: | |
191 | myreturn = re.sub("\n", "", value) | |
192 | return myreturn | |
193 | ||
194 | def get_text_from_subnode(self, subnode_xpath_expr): | |
195 | """ | |
196 | Finds a subnode in the host node and the retrieves a value from it. | |
197 | ||
198 | @return An attribute value | |
199 | """ | |
200 | sub_node = self.node.find(subnode_xpath_expr) | |
201 | if sub_node is not None: | |
202 | return sub_node.text | |
203 | ||
204 | return None | |
205 | ||
206 | ||
207 | class W3afPlugin(PluginXMLFormat): | |
208 | """ | |
209 | Example plugin to parse w3af output. | |
210 | """ | |
211 | ||
212 | def __init__(self): | |
213 | super().__init__() | |
214 | self.identifier_tag = ["w3af-run", "w3afrun"] | |
215 | self.id = "W3af" | |
216 | self.name = "W3af XML Output Plugin" | |
217 | self.plugin_version = "0.0.2" | |
218 | self.version = "1.7.6" | |
219 | self.framework_version = "1.0.0" | |
220 | self.options = None | |
221 | self._current_output = None | |
222 | self.target = None | |
223 | self._command_regex = re.compile(r'^(w3af|sudo w3af|\.\/w3af).*?') | |
224 | self._completition = { | |
225 | "": "", | |
226 | "-h": "Display this help message.", | |
227 | } | |
228 | ||
229 | ||
230 | def parseOutputString(self, output, debug=False): | |
231 | ||
232 | parser = W3afXmlParser(output) | |
233 | ||
234 | ip = self.resolve(parser.host) | |
235 | h_id = self.createAndAddHost(ip) | |
236 | i_id = self.createAndAddInterface( | |
237 | h_id, ip, ipv4_address=ip, hostname_resolution=[parser.host]) | |
238 | s_id = self.createAndAddServiceToInterface(h_id, i_id, "http", | |
239 | "tcp", | |
240 | ports=[parser.port], | |
241 | status="open") | |
242 | ||
243 | for item in parser.items: | |
244 | v_id = self.createAndAddVulnWebToService(h_id, s_id, item.name, | |
245 | item.detail, pname=item.param, path=item.url, website=parser.host, severity=item.severity, | |
246 | method=item.method, request=item.req, resolution=item.resolution, ref=item.ref, response=item.resp) | |
247 | del parser | |
248 | ||
249 | def resolve(self, host): | |
250 | try: | |
251 | return socket.gethostbyname(host) | |
252 | except: | |
253 | pass | |
254 | return host | |
255 | ||
256 | def processCommandString(self, username, current_path, command_string): | |
257 | return None | |
258 | ||
259 | def setHost(self): | |
260 | pass | |
261 | ||
262 | ||
263 | def createPlugin(): | |
264 | return W3afPlugin() | |
265 | ||
266 | if __name__ == "__main__": | |
267 | import sys | |
268 | import os | |
269 | if len(sys.argv) == 2: | |
270 | report_file = sys.argv[1] | |
271 | if os.path.isfile(report_file): | |
272 | plugin = createPlugin() | |
273 | plugin.processReport(report_file) | |
274 | print(plugin.get_json()) | |
275 | else: | |
276 | print(f"Report not found: {report_file}") | |
277 | else: | |
278 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
279 | ||
280 | # 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 | # 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 | import re | |
7 | import os | |
8 | import socket | |
9 | ||
10 | from urllib.parse import urlparse | |
11 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
12 | try: | |
13 | import xml.etree.cElementTree as ET | |
14 | import xml.etree.ElementTree as ET_ORIG | |
15 | ETREE_VERSION = ET_ORIG.VERSION | |
16 | except ImportError: | |
17 | import xml.etree.ElementTree as ET | |
18 | ETREE_VERSION = ET.VERSION | |
19 | ||
20 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] | |
21 | ||
22 | current_path = os.path.abspath(os.getcwd()) | |
23 | ||
24 | __author__ = "Francisco Amato" | |
25 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
26 | __credits__ = ["Francisco Amato"] | |
27 | __license__ = "" | |
28 | __version__ = "1.0.0" | |
29 | __maintainer__ = "Francisco Amato" | |
30 | __email__ = "[email protected]" | |
31 | __status__ = "Development" | |
32 | ||
33 | ||
34 | class WapitiXmlParser: | |
35 | """ | |
36 | The objective of this class is to parse an xml file generated by the wapiti tool. | |
37 | ||
38 | TODO: Handle errors. | |
39 | TODO: Test wapiti output version. Handle what happens if the parser doesn't support it. | |
40 | TODO: Test cases. | |
41 | ||
42 | @param wapiti_xml_filepath A proper xml generated by wapiti | |
43 | """ | |
44 | ||
45 | def __init__(self, xml_output): | |
46 | tree = self.parse_xml(xml_output) | |
47 | if tree: | |
48 | self.items = [data for data in self.get_items(tree)] | |
49 | else: | |
50 | self.items = [] | |
51 | ||
52 | def parse_xml(self, xml_output): | |
53 | """ | |
54 | Open and parse an xml file. | |
55 | ||
56 | TODO: Write custom parser to just read the nodes that we need instead of | |
57 | reading the whole file. | |
58 | ||
59 | @return xml_tree An xml tree instance. None if error. | |
60 | """ | |
61 | try: | |
62 | tree = ET.fromstring(xml_output) | |
63 | except SyntaxError as err: | |
64 | print("SyntaxError: %s. %s" % (err, xml_output)) | |
65 | return None | |
66 | ||
67 | return tree | |
68 | ||
69 | def get_items(self, tree): | |
70 | """ | |
71 | @return items A list of Host instances | |
72 | """ | |
73 | ||
74 | yield Item(tree) | |
75 | ||
76 | ||
77 | ||
78 | def get_attrib_from_subnode(xml_node, subnode_xpath_expr, attrib_name): | |
79 | """ | |
80 | Finds a subnode in the item node and the retrieves a value from it | |
81 | ||
82 | @return An attribute value | |
83 | """ | |
84 | global ETREE_VERSION | |
85 | node = None | |
86 | ||
87 | if ETREE_VERSION[0] <= 1 and ETREE_VERSION[1] < 3: | |
88 | match_obj = re.search( | |
89 | "([^\@]+?)\[\@([^=]*?)=\'([^\']*?)\'", subnode_xpath_expr) | |
90 | if match_obj is not None: | |
91 | node_to_find = match_obj.group(1) | |
92 | xpath_attrib = match_obj.group(2) | |
93 | xpath_value = match_obj.group(3) | |
94 | for node_found in xml_node.findall(node_to_find): | |
95 | if node_found.attrib[xpath_attrib] == xpath_value: | |
96 | node = node_found | |
97 | break | |
98 | else: | |
99 | node = xml_node.find(subnode_xpath_expr) | |
100 | else: | |
101 | node = xml_node.find(subnode_xpath_expr) | |
102 | if node is not None: | |
103 | return node.get(attrib_name) | |
104 | return None | |
105 | ||
106 | ||
107 | class Item: | |
108 | """ | |
109 | An abstract representation of a Item | |
110 | ||
111 | TODO: Consider evaluating the attributes lazily | |
112 | TODO: Write what's expected to be present in the nodes | |
113 | TODO: Refactor both Host and the Port clases? | |
114 | ||
115 | @param item_node A item_node taken from an wapiti xml tree | |
116 | """ | |
117 | ||
118 | def __init__(self, item_node): | |
119 | self.node = item_node | |
120 | self.url = self.get_url(item_node) | |
121 | self.ip = socket.gethostbyname(self.url.hostname) | |
122 | self.hostname = self.url.hostname | |
123 | self.port = self.get_port(self.url) | |
124 | self.scheme = self.url.scheme | |
125 | self.vulns = self.get_vulns(item_node) | |
126 | ||
127 | def do_clean(self, value): | |
128 | myreturn = "" | |
129 | if value is not None: | |
130 | myreturn = re.sub("\n", "", value) | |
131 | return myreturn | |
132 | ||
133 | def get_text_from_subnode(self, node, subnode_xpath_expr): | |
134 | """ | |
135 | Finds a subnode in the host node and the retrieves a value from it. | |
136 | ||
137 | @return An attribute value | |
138 | """ | |
139 | sub_node = node.find(subnode_xpath_expr) | |
140 | if sub_node is not None: | |
141 | return sub_node.text.strip() | |
142 | ||
143 | return None | |
144 | ||
145 | def get_url(self, item_node): | |
146 | target = self.get_info(item_node, 'target') | |
147 | return urlparse(target) | |
148 | ||
149 | def get_info(self, item_node,name): | |
150 | path = item_node.findall('report_infos/info') | |
151 | ||
152 | for item in path: | |
153 | if item.attrib['name'] == name: | |
154 | return item.text | |
155 | ||
156 | def get_port(self, url): | |
157 | if url.port: | |
158 | return url.port | |
159 | else: | |
160 | if url.scheme == "http": | |
161 | return "80" | |
162 | elif url.scheme == "https": | |
163 | return "443" | |
164 | ||
165 | def get_vulns(self, item_node): | |
166 | vulns_node = item_node.findall('vulnerabilities/vulnerability') | |
167 | vulns_list = [] | |
168 | ||
169 | for vuln in vulns_node: | |
170 | vulns_dict = {} | |
171 | vulns_dict['id'] = vuln.attrib['name'] | |
172 | vulns_dict['description'] = self.get_text_from_subnode(vuln,'description') | |
173 | vulns_dict['solution'] = self.get_text_from_subnode(vuln,'solution') | |
174 | vulns_dict['references'] = self.get_references(vuln) | |
175 | vulns_dict['entries'] = self.get_entries(vuln) | |
176 | vulns_list.append(vulns_dict) | |
177 | ||
178 | return vulns_list | |
179 | ||
180 | def get_references(self, node): | |
181 | refs = node.findall('references/reference') | |
182 | references_list = [] | |
183 | for ref in refs: | |
184 | references_list.append('Title: ' + self.get_text_from_subnode(ref,'title')) | |
185 | references_list.append('URL: ' + self.get_text_from_subnode(ref,'url')) | |
186 | ||
187 | return references_list | |
188 | ||
189 | def get_entries(self,node): | |
190 | entries = node.findall('entries/entry') | |
191 | entries_list = [] | |
192 | for entry in entries: | |
193 | entries_dict = {} | |
194 | entries_dict['method'] = self.get_text_from_subnode(entry,'method') | |
195 | entries_dict['path'] = self.get_text_from_subnode(entry,'path') | |
196 | entries_dict['level'] = self.severity_format(entry) | |
197 | entries_dict['parameter'] = self.get_text_from_subnode(entry,'parameter') | |
198 | entries_dict['http_request'] = self.get_text_from_subnode(entry,'http_request') | |
199 | entries_dict['curl_command'] = self.get_text_from_subnode(entry,'curl_command') | |
200 | entries_list.append(entries_dict) | |
201 | ||
202 | return entries_list | |
203 | ||
204 | def severity_format(self, node): | |
205 | """ | |
206 | Convert Nexpose severity format into Faraday API severity format | |
207 | ||
208 | @return a severity | |
209 | """ | |
210 | severity = self.get_text_from_subnode(node, 'level') | |
211 | ||
212 | if severity == '1': | |
213 | return 'high' | |
214 | elif severity == '2': | |
215 | return 'medium' | |
216 | elif severity == '3': | |
217 | return 'low' | |
218 | ||
219 | ||
220 | class WapitiPlugin(PluginXMLFormat): | |
221 | """ | |
222 | Example plugin to parse wapiti output. | |
223 | """ | |
224 | ||
225 | def __init__(self): | |
226 | super().__init__() | |
227 | self.identifier_tag = "report" | |
228 | self.id = "Wapiti" | |
229 | self.name = "Wapiti XML Output Plugin" | |
230 | self.plugin_version = "0.0.1" | |
231 | self.version = "2.2.1" | |
232 | self.options = None | |
233 | self._current_output = None | |
234 | self.protocol = None | |
235 | self.host = None | |
236 | self.port = "80" | |
237 | self.xml_arg_re = re.compile(r"^.*(-oX\s*[^\s]+).*$") | |
238 | self._command_regex = re.compile( | |
239 | r'^(python wapiti|wapiti|sudo wapiti|sudo wapiti\.py|wapiti\.py|python wapiti\.py|\.\/wapiti\.py|wapiti|\.\/wapiti|python wapiti|python \.\/wapiti).*?') | |
240 | self._completition = { | |
241 | "": "python wapiti.py http://server.com/base/url/ [options]", | |
242 | "-s": "<url> ", | |
243 | "--start": "<url> ", | |
244 | "-x": "<url> ", | |
245 | "--exclude": "<url> ", | |
246 | "-p": "<url_proxy> ", | |
247 | "--proxy": "<url_proxy> ", | |
248 | "-c": " -c <cookie_file> ", | |
249 | "--cookie": "<cookie_file> ", | |
250 | "-t": "<timeout> ", | |
251 | "--timeout": "<timeout> ", | |
252 | "-a": "<login%password> ", | |
253 | "--auth": "<login%password> ", | |
254 | "-r": "<parameter_name> ", | |
255 | "--remove": "<parameter_name> ", | |
256 | "-n": "<limit> ", | |
257 | "--nice": "<limit> ", | |
258 | "-m": "<module_options> Set the modules and HTTP methods to use for attacks. Example: -m \"-all,xss:get,exec:post\"", | |
259 | "--module": "<module_options> Set the modules and HTTP methods to use for attacks. Example: -m \"-all,xss:get,exec:post\"", | |
260 | "-u": "Use color to highlight vulnerables parameters in output", | |
261 | "--underline": "Use color to highlight vulnerables parameters in output", | |
262 | "-v": "<level> ", | |
263 | "--verbose": "<level> ", | |
264 | "-b": "<scope>", | |
265 | "--scope": "<scope>", | |
266 | "-f": "<type_file> ", | |
267 | "--reportType": "<type_file> ", | |
268 | "-o": "<output_file> ", | |
269 | "--output": "<output_file> ", | |
270 | "-i": "<file>", | |
271 | "--continue": "<file>", | |
272 | "-k": "<file>", | |
273 | "--attack": "<file>", | |
274 | "-h": "To print this usage message", | |
275 | "--help": "To print this usage message", | |
276 | } | |
277 | ||
278 | def report_belongs_to(self, **kwargs): | |
279 | if super().report_belongs_to(**kwargs): | |
280 | report_path = kwargs.get("report_path", "") | |
281 | with open(report_path) as f: | |
282 | output = f.read() | |
283 | return re.search("Wapiti", output) is not None | |
284 | return False | |
285 | ||
286 | ||
287 | def parseOutputString(self, output): | |
288 | """ | |
289 | This method will discard the output the shell sends, it will read it from | |
290 | the xml where it expects it to be present. | |
291 | """ | |
292 | ||
293 | parser = WapitiXmlParser(output) | |
294 | for item in parser.items: | |
295 | host_id = self.createAndAddHost(item.ip, hostnames=[item.hostname]) | |
296 | service_id = self.createAndAddServiceToHost(host_id, item.scheme, protocol='tcp', ports=[item.port]) | |
297 | for vuln in item.vulns: | |
298 | for entry in vuln['entries']: | |
299 | vuln_id = self.createAndAddVulnWebToService(host_id, | |
300 | service_id, | |
301 | vuln['id'], | |
302 | desc=vuln['description'], | |
303 | ref=vuln['references'], | |
304 | resolution=vuln['solution'], | |
305 | severity=entry['level'], | |
306 | website=entry['curl_command'], | |
307 | path=entry['path'], | |
308 | request=entry['http_request'], | |
309 | method=entry['method'], | |
310 | params=entry['parameter']) | |
311 | ||
312 | def processCommandString(self, username, current_path, command_string): | |
313 | """ | |
314 | Adds the -oX parameter to get xml output to the command string that the | |
315 | user has set. | |
316 | """ | |
317 | host = re.search( | |
318 | "(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\.\,\?\'\\\+&%\$#\=~_\-]+)).*?$", command_string) | |
319 | self.protocol = host.group(1) | |
320 | self.host = host.group(4) | |
321 | if host.group(11) is not None: | |
322 | self.port = host.group(11) | |
323 | if self.protocol == 'https': | |
324 | self.port = 443 | |
325 | self.logger.debug("host = %s, port = %s",self.host, self.port) | |
326 | arg_match = self.xml_arg_re.match(command_string) | |
327 | return "%s -o %s -f xml \n" % (command_string, self._output_file_path) | |
328 | ||
329 | def setHost(self): | |
330 | pass | |
331 | ||
332 | ||
333 | def createPlugin(): | |
334 | return WapitiPlugin() | |
335 | ||
336 | ||
337 | if __name__ == "__main__": | |
338 | import sys | |
339 | import os | |
340 | if len(sys.argv) == 2: | |
341 | report_file = sys.argv[1] | |
342 | if os.path.isfile(report_file): | |
343 | plugin = createPlugin() | |
344 | plugin.processReport(report_file) | |
345 | print(plugin.get_json()) | |
346 | else: | |
347 | print(f"Report not found: {report_file}") | |
348 | else: | |
349 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
350 | # 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 | # 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 | from faraday_plugins.plugins.plugin import PluginBase | |
6 | import re | |
7 | import os | |
8 | import sys | |
9 | import random | |
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 | |
18 | ||
19 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] | |
20 | ||
21 | current_path = os.path.abspath(os.getcwd()) | |
22 | ||
23 | __author__ = "Morgan Lemarechal" | |
24 | __copyright__ = "Copyright 2014, Faraday Project" | |
25 | __credits__ = ["Morgan Lemarechal"] | |
26 | __license__ = "" | |
27 | __version__ = "1.0.0" | |
28 | __maintainer__ = "Morgan Lemarechal" | |
29 | __email__ = "[email protected]" | |
30 | __status__ = "Development" | |
31 | ||
32 | ||
33 | class WcscanParser: | |
34 | """ | |
35 | The objective of this class is to parse an xml file generated by the wcscan tool. | |
36 | TODO: Handle errors. | |
37 | TODO: Test wcscan output version. Handle what happens if the parser doesn't support it. | |
38 | TODO: Test cases. | |
39 | @param wcscan_filepath A proper simple report generated by wcscan | |
40 | """ | |
41 | ||
42 | def __init__(self, output): | |
43 | self.scaninfo = {} | |
44 | self.result = {} | |
45 | tree = ET.parse(output) | |
46 | root = tree.getroot() | |
47 | for scan in root.findall(".//scan"): | |
48 | infos = {} | |
49 | for info in scan.attrib: | |
50 | infos[info] = scan.attrib[info] | |
51 | self.scaninfo[scan.attrib['file']] = infos | |
52 | ||
53 | item = {} | |
54 | if scan.attrib['type'] == "phpini": | |
55 | for carac in scan: | |
56 | item[carac.tag] = [carac.text, carac.attrib['rec'], ""] | |
57 | ||
58 | if scan.attrib['type'] == "webconfig": | |
59 | id = 0 | |
60 | for carac in scan: | |
61 | id += 1 | |
62 | item[id] = [carac.text, carac.attrib['rec'], | |
63 | carac.attrib['option'], carac.tag] | |
64 | ||
65 | self.result[scan.attrib['file']] = item | |
66 | ||
67 | ||
68 | class WcscanPlugin(PluginBase): | |
69 | """ | |
70 | Example plugin to parse wcscan output. | |
71 | """ | |
72 | ||
73 | def __init__(self): | |
74 | super().__init__() | |
75 | self.id = "Wcscan" | |
76 | self.name = "Wcscan XML Output Plugin" | |
77 | self.plugin_version = "0.0.2" | |
78 | self.version = "0.30" | |
79 | self._completition = { | |
80 | "": "wcscan [-h] [-r] [-host HOST] [-port PORT] [--xml XMLOUTPUT] [--version] files [files ...]", | |
81 | "-h": "show this help message and exit", | |
82 | "-r": "enable the recommendation mode", | |
83 | "--host": "to give the IP address of the conf file owner", | |
84 | "--port": "to give a associated port", | |
85 | "--xml": "enabled the XML output in a specified file", | |
86 | "--version": "Show program's version number and exit", | |
87 | } | |
88 | ||
89 | 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).*?') | |
94 | ||
95 | ||
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): | |
103 | """ | |
104 | This method will discard the output the shell sends, it will read it from | |
105 | the xml where it expects it to be present. | |
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 | if debug: | |
110 | parser = WcscanParser(self._output_file_path) | |
111 | else: | |
112 | ||
113 | if not os.path.exists(self._output_file_path): | |
114 | return False | |
115 | parser = WcscanParser(self._output_file_path) | |
116 | ||
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) | |
129 | ||
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 | ||
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 | ||
160 | def processCommandString(self, username, current_path, command_string): | |
161 | """ | |
162 | Adds the parameter to get output to the command string that the | |
163 | user has set. | |
164 | """ | |
165 | ||
166 | arg_match = self.xml_arg_re.match(command_string) | |
167 | ||
168 | if arg_match is None: | |
169 | return "%s --xml %s" % (command_string, self._output_file_path) | |
170 | else: | |
171 | return re.sub(arg_match.group(1), | |
172 | r"-xml %s" % self._output_file_path, | |
173 | command_string) | |
174 | ||
175 | ||
176 | def createPlugin(): | |
177 | return WcscanPlugin() | |
178 | ||
179 | if __name__ == "__main__": | |
180 | import sys | |
181 | import os | |
182 | if len(sys.argv) == 2: | |
183 | report_file = sys.argv[1] | |
184 | if os.path.isfile(report_file): | |
185 | plugin = createPlugin() | |
186 | plugin.processReport(report_file) | |
187 | print(plugin.get_json()) | |
188 | else: | |
189 | print(f"Report not found: {report_file}") | |
190 | else: | |
191 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
192 | ||
193 | # 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 | # 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 | ||
10 | ||
11 | ||
12 | current_path = os.path.abspath(os.getcwd()) | |
13 | ||
14 | __author__ = "Francisco Amato" | |
15 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
16 | __credits__ = ["Francisco Amato"] | |
17 | __license__ = "" | |
18 | __version__ = "1.0.0" | |
19 | __maintainer__ = "Francisco Amato" | |
20 | __email__ = "[email protected]" | |
21 | __status__ = "Development" | |
22 | ||
23 | ||
24 | class WebfuzzerParser: | |
25 | """ | |
26 | The objective of this class is to parse an xml file generated by the webfuzzer tool. | |
27 | ||
28 | TODO: Handle errors. | |
29 | TODO: Test webfuzzer output version. Handle what happens if the parser doesn't support it. | |
30 | TODO: Test cases. | |
31 | ||
32 | @param webfuzzer_filepath A proper output generated by webfuzzer | |
33 | """ | |
34 | ||
35 | def __init__(self, webfuzzer_filepath): | |
36 | self.filepath = webfuzzer_filepath | |
37 | ||
38 | with open(self.filepath, "r") as f: | |
39 | try: | |
40 | data = f.read() | |
41 | f.close() | |
42 | m = re.search( | |
43 | "Scan of ([\w.]+):([\d]+) \[([/\w]+)\] \(([\w.]+)\)", data) | |
44 | self.hostname = m.group(1) | |
45 | self.port = m.group(2) | |
46 | self.uri = m.group(3) | |
47 | self.ipaddress = m.group(4) | |
48 | ||
49 | m = re.search("Server header:\n\n([\w\W]+)\n\n\n", data) | |
50 | self.header = m.group(1) | |
51 | ||
52 | self.items = [] | |
53 | ||
54 | pattern = r'\((POST|GET)\): ([\w\W]*?) \]--' | |
55 | ||
56 | for m in re.finditer(pattern, data, re.DOTALL): | |
57 | ||
58 | method = m.group(1) | |
59 | info = re.search( | |
60 | "^([\w\W]+)\(([\w\W]+)\)\n--\[ ([\w\W]+)$", m.group(2)) | |
61 | ||
62 | vuln = {'method': m.group(1), 'desc': info.group( | |
63 | 1), 'url': info.group(2), 'resp': info.group(3)} | |
64 | self.items.append(vuln) | |
65 | ||
66 | except SyntaxError as err: | |
67 | print("SyntaxError: %s. %s" % (err, self.filepath)) | |
68 | return None | |
69 | ||
70 | ||
71 | class WebfuzzerPlugin(PluginBase): | |
72 | """ | |
73 | Example plugin to parse webfuzzer output. | |
74 | """ | |
75 | ||
76 | def __init__(self): | |
77 | super().__init__() | |
78 | self.id = "Webfuzzer" | |
79 | self.name = "Webfuzzer Output Plugin" | |
80 | self.plugin_version = "0.0.2" | |
81 | self.version = "0.2.0" | |
82 | self.options = None | |
83 | self._current_output = None | |
84 | self.host = None | |
85 | self._command_regex = re.compile( | |
86 | r'^(sudo webfuzzer|webfuzzer|\.\/webfuzzer).*?') | |
87 | self._completition = {'': '__Usage: ./webfuzzer -G|-P URL [OPTIONS]', | |
88 | '-G': '<url> get this as starting url (with parameters)', | |
89 | '-P': '<url> post this as starting url (with parameters)', | |
90 | '-x': 'html output (txt default)', | |
91 | '-c': 'use cookies', | |
92 | '-C': '<cookies> set this cookie(s) **', | |
93 | '-s': 'check for sql, asp, vb, php errors (default)', | |
94 | '-d': 'check for directory traversal *', | |
95 | '-p': 'check for insecure perl open or xss *', | |
96 | '-e': 'check for execution through shell escapes or xss *', | |
97 | '-a': 'set all of the above switches on *', | |
98 | } | |
99 | ||
100 | self._output_path = None | |
101 | ||
102 | def parseOutputString(self, output, debug=False): | |
103 | """ | |
104 | This method will discard the output the shell sends, it will read it from | |
105 | the xml where it expects it to be present. | |
106 | ||
107 | NOTE: if 'debug' is true then it is being run from a test case and the | |
108 | output being sent is valid. | |
109 | """ | |
110 | ||
111 | if self._output_path is None: | |
112 | return False | |
113 | else: | |
114 | if not os.path.exists(self._output_path): | |
115 | return False | |
116 | ||
117 | parser = WebfuzzerParser(self._output_path) | |
118 | ||
119 | h_id = self.createAndAddHost(parser.ipaddress) | |
120 | ||
121 | i_id = self.createAndAddInterface( | |
122 | h_id, parser.ipaddress, ipv4_address=parser.ipaddress, hostname_resolution=[parser.hostname]) | |
123 | ||
124 | first = True | |
125 | for item in parser.items: | |
126 | if first: | |
127 | s_id = self.createAndAddServiceToInterface(h_id, i_id, parser.port, | |
128 | "tcp", | |
129 | ports=[parser.port]) | |
130 | first = False | |
131 | ||
132 | v_id = self.createAndAddVulnWebToService(h_id, s_id, name=item['desc'], | |
133 | path=item['url'], response=item[ | |
134 | 'resp'], | |
135 | method=item['method'], website=parser.hostname) | |
136 | ||
137 | del parser | |
138 | ||
139 | return True | |
140 | ||
141 | def processCommandString(self, username, current_path, command_string): | |
142 | """ | |
143 | """ | |
144 | host = re.search("\-([G|P]) ([\w\.\-]+)", command_string) | |
145 | ||
146 | if host is not None: | |
147 | self.host = host.group(2) | |
148 | self._output_path = current_path + "/" + self.host + ".txt" | |
149 | ||
150 | ||
151 | def createPlugin(): | |
152 | return WebfuzzerPlugin() | |
153 | ||
154 | if __name__ == "__main__": | |
155 | import sys | |
156 | import os | |
157 | if len(sys.argv) == 2: | |
158 | report_file = sys.argv[1] | |
159 | if os.path.isfile(report_file): | |
160 | plugin = createPlugin() | |
161 | plugin.processReport(report_file) | |
162 | print(plugin.get_json()) | |
163 | else: | |
164 | print(f"Report not found: {report_file}") | |
165 | else: | |
166 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
167 | ||
168 | # I'm Py3 |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2018 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | """ | |
5 | import re | |
6 | ||
7 | from faraday_plugins.plugins.plugin import PluginBase | |
8 | from faraday_plugins.plugins.plugins_utils import get_vulnweb_url_fields | |
9 | ||
10 | try: | |
11 | import xml.etree.ElementTree as ET | |
12 | except ImportError: | |
13 | import xml.etree.ElementTree as ET | |
14 | ||
15 | ||
16 | def cleanhtml(raw_html): | |
17 | cleanr = re.compile('<.*?>') | |
18 | cleantext = re.sub(cleanr, '', raw_html) | |
19 | return cleantext | |
20 | ||
21 | ||
22 | class WebInspectParser(): | |
23 | ||
24 | def __init__(self, output): | |
25 | self.xml = ET.fromstring(output) | |
26 | self.issues = self.xml.findall("Issues/Issue") | |
27 | ||
28 | ||
29 | def parse_severity(self, severity): | |
30 | ||
31 | severity_dict = { | |
32 | "0": "info", | |
33 | "1": "low", | |
34 | "2": "med", | |
35 | "3": "high", | |
36 | "4": "critical"} | |
37 | ||
38 | result = severity_dict.get(severity) | |
39 | if not result: | |
40 | return "info" | |
41 | else: | |
42 | return result | |
43 | ||
44 | def return_text(self, tag,element): | |
45 | try: | |
46 | text = element.find(tag).text.encode("ascii", errors="backslashreplace") | |
47 | return text | |
48 | except: | |
49 | return "" | |
50 | ||
51 | def parse(self): | |
52 | ||
53 | map_objects_fields = { | |
54 | "Name": ["Vuln", "name"], | |
55 | "URL": ["Vuln", "website"], | |
56 | "Scheme": ["Service", "name"], | |
57 | "Host": ["Host", "name"], | |
58 | "Port": ["Service", "port"], | |
59 | "AttackMethod": ["Vuln", "method"], | |
60 | "VulnerableSession": ["Vuln", "request"], | |
61 | "VulnerabilityID": ["Vuln", "reference"], | |
62 | "RawResponse": ["Vuln", "response"], | |
63 | "Summary": ["Vuln", "description"], | |
64 | "Implication": ["Vuln", "data"], | |
65 | "Fix": ["Vuln", "resolution"], | |
66 | "Reference Info": ["Vuln", "reference"], | |
67 | "Severity": ["Vuln", "severity"] | |
68 | } | |
69 | ||
70 | result = [] | |
71 | for issue in self.issues: | |
72 | ||
73 | obj = { | |
74 | "Host" : {}, | |
75 | "Service" : {}, | |
76 | "Interface" : {}, | |
77 | "Vuln": { | |
78 | "reference" : []} | |
79 | } | |
80 | ||
81 | for tag, obj_property in map_objects_fields.items(): | |
82 | ||
83 | value = self.return_text(tag,issue) | |
84 | ||
85 | if value is not None: | |
86 | ||
87 | faraday_obj_name = obj_property[0] | |
88 | faraday_field = obj_property[1] | |
89 | if faraday_field == "reference": | |
90 | obj[faraday_obj_name].get("reference").append(value) | |
91 | else: | |
92 | obj[faraday_obj_name].update({faraday_field:value}) | |
93 | ||
94 | # This for loads Summary, Implication, Fix and Reference | |
95 | for section in issue.findall("ReportSection"): | |
96 | ||
97 | try: | |
98 | field = section.find("Name").text.encode("ascii", errors="backslashreplace") | |
99 | value = section.find("SectionText").text.encode("ascii", errors="backslashreplace") | |
100 | ||
101 | faraday_obj_name = map_objects_fields.get(field)[0] | |
102 | faraday_field = map_objects_fields.get(field)[1] | |
103 | except: | |
104 | continue | |
105 | ||
106 | if faraday_field == "reference" and value != "": | |
107 | obj[faraday_obj_name].get("reference").append(cleanhtml(value)) | |
108 | else: | |
109 | obj[faraday_obj_name].update({faraday_field:value}) | |
110 | ||
111 | result.append(obj) | |
112 | return result | |
113 | ||
114 | ||
115 | class WebInspectPlugin(PluginBase): | |
116 | """ | |
117 | This plugin handles WebInspect reports. | |
118 | """ | |
119 | ||
120 | def __init__(self): | |
121 | super().__init__() | |
122 | self.id = "Webinspect" | |
123 | self.name = "Webinspect" | |
124 | self.plugin_version = "0.0.1" | |
125 | self.version = "1.0.0" | |
126 | ||
127 | def parseOutputString(self, output, debug=False): | |
128 | ||
129 | parser = WebInspectParser(output) | |
130 | vulns = parser.parse() | |
131 | ||
132 | for vuln in vulns: | |
133 | ||
134 | host_id = self.createAndAddHost( | |
135 | vuln.get("Host").get("name")) | |
136 | ||
137 | interface_id = self.createAndAddInterface( | |
138 | host_id, vuln.get("Host").get("name")) | |
139 | ||
140 | service_id = self.createAndAddServiceToInterface( | |
141 | host_id, interface_id, | |
142 | vuln.get("Service").get("name"), | |
143 | protocol=vuln.get("Service").get("name"), | |
144 | ports=[vuln.get("Service").get("port")]) | |
145 | ||
146 | self.createAndAddVulnWebToService( | |
147 | host_id, service_id, | |
148 | vuln.get("Vuln").get("name"), | |
149 | website=get_vulnweb_url_fields(vuln.get("Vuln").get("website")).get("website"), | |
150 | path=get_vulnweb_url_fields(vuln.get("Vuln").get("website")).get("path"), | |
151 | query=get_vulnweb_url_fields(vuln.get("Vuln").get("website")).get("query"), | |
152 | method=vuln.get("Vuln").get("method"), | |
153 | request=vuln.get("Vuln").get("request"), | |
154 | ref=list(filter(None ,vuln.get("Vuln").get("reference"))), | |
155 | response=vuln.get("Vuln").get("response"), | |
156 | desc=cleanhtml(vuln.get("Vuln").get("description")), | |
157 | resolution=cleanhtml(vuln.get("Vuln").get("resolution")), | |
158 | severity=parser.parse_severity(vuln.get("Vuln").get("severity")) | |
159 | ) | |
160 | ||
161 | return True | |
162 | ||
163 | def processCommandString(self, username, current_path, command_string): | |
164 | return None | |
165 | ||
166 | ||
167 | def createPlugin(): | |
168 | return WebInspectPlugin() | |
169 | ||
170 | ||
171 | if __name__ == "__main__": | |
172 | import sys | |
173 | import os | |
174 | if len(sys.argv) == 2: | |
175 | report_file = sys.argv[1] | |
176 | if os.path.isfile(report_file): | |
177 | plugin = createPlugin() | |
178 | plugin.processReport(report_file) | |
179 | print(plugin.get_json()) | |
180 | else: | |
181 | print(f"Report not found: {report_file}") | |
182 | else: | |
183 | print(f"USAGE {sys.argv[0]} REPORT_FILE") |
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 | import re | |
1 | import string | |
2 | from urllib.parse import urljoin, urlparse | |
3 | ||
4 | from faraday_plugins.plugins.plugin import PluginBase | |
5 | ||
6 | ||
7 | class WfuzzPlugin(PluginBase): | |
8 | ||
9 | def __init__(self): | |
10 | super().__init__() | |
11 | self.id = "Wfuzz" | |
12 | self.name = "Wfuzz Plugin" | |
13 | self.plugin_version = "0.0.1" | |
14 | self.version = "2.2.11" | |
15 | self.options = None | |
16 | ||
17 | self.host = None | |
18 | self.port = None | |
19 | self.protocol = None | |
20 | self.fail = None | |
21 | self._command_regex = re.compile( | |
22 | r'^(wfuzz).*?') | |
23 | ||
24 | def parseData(self, output): | |
25 | ||
26 | data = { | |
27 | 'target' : '', | |
28 | 'findings' : [] | |
29 | } | |
30 | for line in output: | |
31 | # remove stdout hidden chars | |
32 | line = ''.join([char for char in line if char in string.printable]) | |
33 | line = line.strip('\r').replace('[0K', '').replace('[0m', '') | |
34 | if line.startswith('Target'): | |
35 | data['target'] = line[8:].rstrip() | |
36 | continue | |
37 | if line.startswith('0'): | |
38 | aux = line.split(' ') | |
39 | res = {} | |
40 | for item in aux: | |
41 | if 'C=' in item: | |
42 | res['response'] = int(item.replace('C=', '')) | |
43 | elif 'L' in item and ' ' in item: | |
44 | res['lines'] = int(item.replace('L', '')) | |
45 | elif 'W' in item and ' ' in item: | |
46 | res['words'] = int(item.replace('W', '')) | |
47 | elif 'Ch' in item and ' ' in item: | |
48 | res['chars'] = int(item.replace('Ch', '')) | |
49 | else: | |
50 | res['request'] = item.rstrip().replace('"', '') | |
51 | data['findings'].append(res) | |
52 | ||
53 | return data | |
54 | ||
55 | def parseOutputString(self, output, debug=False): | |
56 | output_list = output.split('\n') | |
57 | info = self.parseData(output_list) | |
58 | ||
59 | target = info['target'] | |
60 | target_url = urlparse(target) | |
61 | port = 80 | |
62 | ||
63 | if target_url.scheme == 'https': | |
64 | port = 443 | |
65 | custom_port = target_url.netloc.split(':') | |
66 | if len(custom_port) > 1: | |
67 | port = custom_port[1] | |
68 | ||
69 | host_id = self.createAndAddHost(target) | |
70 | ||
71 | service_id = self.createAndAddServiceToHost(host_id,name="http",protocol="tcp", ports=[port] ) | |
72 | ||
73 | for item in info['findings']: | |
74 | path = item['request'] | |
75 | status = item['response'] | |
76 | url = urljoin(target, path) | |
77 | lines = item['lines'] | |
78 | chars = item['chars'] | |
79 | words = item['words'] | |
80 | name = "Wfuzz found: {path} with status {status} on url {url}".format(path=path, status=status, url=url) | |
81 | desc = 'Wfuzz found a response with status {status}. Response contains: \n* {words} words \n* {lines} lines \n* {chars} chars'.format( | |
82 | words=words, | |
83 | url=url, | |
84 | lines=lines, | |
85 | chars=chars, | |
86 | status=status | |
87 | ) | |
88 | self.createAndAddVulnWebToService(host_id, | |
89 | service_id, | |
90 | name, | |
91 | desc, | |
92 | severity="info", | |
93 | website=target, | |
94 | path=path | |
95 | ) | |
96 | ||
97 | ||
98 | def createPlugin(): | |
99 | return WfuzzPlugin() | |
100 | ||
101 | ||
102 | if __name__ == "__main__": | |
103 | import sys | |
104 | import os | |
105 | if len(sys.argv) == 2: | |
106 | report_file = sys.argv[1] | |
107 | if os.path.isfile(report_file): | |
108 | plugin = createPlugin() | |
109 | plugin.processReport(report_file) | |
110 | print(plugin.get_json()) | |
111 | else: | |
112 | print(f"Report not found: {report_file}") | |
113 | else: | |
114 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
115 | ||
116 | ||
117 | # 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 | # 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 | current_path = os.path.abspath(os.getcwd()) | |
11 | ||
12 | __author__ = "Facundo de Guzmán, Esteban Guillardoy" | |
13 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
14 | __credits__ = ["Facundo de Guzmán", "Esteban Guillardoy"] | |
15 | __license__ = "" | |
16 | __version__ = "1.0.0" | |
17 | __maintainer__ = "Federico Kirschbaum" | |
18 | __email__ = "[email protected]" | |
19 | __status__ = "Development" | |
20 | ||
21 | ||
22 | class CmdWhoisPlugin(PluginBase): | |
23 | """ | |
24 | This plugin handles whois command. | |
25 | Basically detects if user was able to connect to a device | |
26 | """ | |
27 | ||
28 | def __init__(self): | |
29 | super().__init__() | |
30 | self.id = "whois" | |
31 | self.name = "Whois" | |
32 | self.plugin_version = "0.0.1" | |
33 | self.version = "5.0.20" | |
34 | self.framework_version = "1.0.0" | |
35 | self.options = None | |
36 | self._current_output = None | |
37 | self._command_regex = re.compile(r'^whois.*?') | |
38 | self._host_ip = None | |
39 | self._info = 0 | |
40 | self._completition = { | |
41 | "": "whois [OPTION]... OBJECT...", | |
42 | "-l": "one level less specific lookup [RPSL only]", | |
43 | "-L": "find all Less specific matches", | |
44 | "-m": "find first level more specific matches", | |
45 | "-M": "find all More specific matches", | |
46 | "-c": "find the smallest match containing a mnt-irt attribute", | |
47 | "-x": "exact match [RPSL only]", | |
48 | "-d": "return DNS reverse delegation objects too [RPSL only]", | |
49 | "-i": "-i ATTR[,ATTR]... do an inverse lookup for specified ATTRibutes", | |
50 | "-T": "-T TYPE[,TYPE]... only look for objects of TYPE", | |
51 | "-K": "only primary keys are returned [RPSL only]", | |
52 | "-r": "turn off recursive lookups for contact information", | |
53 | "-R": "force to show local copy of the domain object even if it contains referral", | |
54 | "-a": "search all databases", | |
55 | "-s": "-s SOURCE[,SOURCE]... search the database from SOURCE", | |
56 | "-g": "-g SOURCE:FIRST-LAST find updates from SOURCE from serial FIRST to LAST", | |
57 | "-t": "-t TYPE request template for object of TYPE", | |
58 | "-v": "-v TYPE request verbose template for object of TYPE", | |
59 | "-q": "-q [version|sources|types] query specified server info [RPSL only]", | |
60 | "-F": "fast raw output (implies -r)", | |
61 | "-h": "-h HOST connect to server HOST", | |
62 | "-p": "-p PORT connect to PORT", | |
63 | "-H": "hide legal disclaimers", | |
64 | "--verbose": "explain what is being done", | |
65 | "--help": "display this help and exit", | |
66 | "--version": "output version information and exit", | |
67 | } | |
68 | ||
69 | global current_path | |
70 | ||
71 | def resolve(self, host): | |
72 | try: | |
73 | return socket.gethostbyname(host) | |
74 | except: | |
75 | pass | |
76 | return host | |
77 | ||
78 | def parseOutputString(self, output, debug=False): | |
79 | matches = re.findall("Name Server:\s*(.*)\s*", output) | |
80 | for m in matches: | |
81 | m = m.strip() | |
82 | ip = self.resolve(m) | |
83 | h_id = self.createAndAddHost(ip, "os unknown") | |
84 | i_id = self.createAndAddInterface( | |
85 | h_id, ip, "00:00:00:00:00:00", ip, hostname_resolution=[m]) | |
86 | return True | |
87 | ||
88 | def processCommandString(self, username, current_path, command_string): | |
89 | """ | |
90 | """ | |
91 | return None | |
92 | ||
93 | ||
94 | def createPlugin(): | |
95 | return CmdWhoisPlugin() | |
96 | ||
97 | if __name__ == "__main__": | |
98 | import sys | |
99 | import os | |
100 | if len(sys.argv) == 2: | |
101 | report_file = sys.argv[1] | |
102 | if os.path.isfile(report_file): | |
103 | plugin = createPlugin() | |
104 | plugin.processReport(report_file) | |
105 | print(plugin.get_json()) | |
106 | else: | |
107 | print(f"Report not found: {report_file}") | |
108 | else: | |
109 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
110 | ||
111 | # 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 | # 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 | """ | |
6 | import re | |
7 | import socket | |
8 | import json | |
9 | from faraday_plugins.plugins.plugin import PluginBase | |
10 | ||
11 | ||
12 | __author__ = "Joaquin L. Pereyra | Federico Fernandez" | |
13 | __copyright__ = "Copyright (c) 2016, Infobyte LLC" | |
14 | __credits__ = ["Joaquin L. Pereyra"] | |
15 | __license__ = "" | |
16 | __version__ = "0.0.1" | |
17 | __maintainer__ = "Joaquin L. Pereyra" | |
18 | __email__ = "[email protected]" | |
19 | __status__ = "Development" | |
20 | ||
21 | ||
22 | class WPScanPlugin(PluginBase): | |
23 | """ Handle the WPScan tool. Detects the output of the tool | |
24 | and adds the information to Faraday. | |
25 | """ | |
26 | ||
27 | def __init__(self): | |
28 | """Initalizes the plugin with some basic params. | |
29 | Right now the plugin doesnt support being executed from another folder, | |
30 | like /dir/wpscan.rb | |
31 | """ | |
32 | super().__init__() | |
33 | self.id = "wpscan" | |
34 | self.name = "WPscan" | |
35 | self.plugin_version = "0.2" | |
36 | self.version = "3.4.5" | |
37 | self._command_regex = re.compile( | |
38 | r"^((sudo )?(ruby )?(\.\/)?(wpscan)(.rb)?)") | |
39 | self.wpPath = self.get_wpscan_filepath() | |
40 | self.addSetting("WPscan path", str, self.wpPath) | |
41 | self.themes = {} | |
42 | self.plugins = {} | |
43 | self.wpversion = '' | |
44 | self.risks = {'AUTHBYPASS': 'high', | |
45 | 'BYPASS': 'med', | |
46 | 'CSRF': 'med', | |
47 | 'DOS': 'med', | |
48 | 'FPD': 'info', | |
49 | 'LFI': 'high', | |
50 | 'MULTI': 'unclassified', | |
51 | 'OBJECTINJECTION': 'med', | |
52 | 'PRIVESC': 'high', | |
53 | 'RCE': 'critical', | |
54 | 'REDIRECT': 'low', | |
55 | 'RFI': 'critical', | |
56 | 'SQLI': 'high', | |
57 | 'SSRF': 'med', | |
58 | 'UNKNOWN': 'unclassified', | |
59 | 'UPLOAD': 'critical', | |
60 | 'XSS': 'high', | |
61 | 'XXE': 'high'} | |
62 | ||
63 | def get_wpscan_filepath(self): | |
64 | home = os.path.expanduser("~") | |
65 | ||
66 | wpscan_path = os.path.join(home, '.wpscan') | |
67 | if os.path.exists(wpscan_path): | |
68 | return wpscan_path | |
69 | else: | |
70 | return None | |
71 | ||
72 | def search_file_in_wpscan_folder(self, wp_file): | |
73 | db_path = os.path.join(self.wpPath, 'db', wp_file) | |
74 | data_path = os.path.join(self.wpPath, 'data', wp_file) | |
75 | if os.path.exists(db_path): | |
76 | return db_path | |
77 | elif os.path.exists(data_path): | |
78 | return data_path | |
79 | ||
80 | def getPort(self, host, proto): | |
81 | p = re.search(r"\:([0-9]+)\/", host) | |
82 | if p is not None: | |
83 | return p.group(1) | |
84 | elif proto == 'https': | |
85 | return 443 | |
86 | else: | |
87 | return 80 | |
88 | ||
89 | def parseOutputWpscan(self, output): | |
90 | sp = output.split('0m Name:') # cut by name | |
91 | for e in sp: | |
92 | if 'Title:' in e: | |
93 | if 'WordPress version' in e: | |
94 | r = re.search(r'WordPress version (\d.\w)', e) # get wordpress version | |
95 | self.wpversion = r.group(1) | |
96 | ||
97 | elif 'wp-content/themes/' in e: | |
98 | name = re.findall(r"Location: .+themes\/(.+)\/", e) # get theme name | |
99 | title = re.findall(r"Title: (.+)", e) # get vulnerabilities title | |
100 | self.themes[name[0]] = title # insert theme in dicc {'themeName' : ['titles', 'titles']} | |
101 | ||
102 | else: | |
103 | name = re.findall(r"Location: .+plugins\/(.+)\/", e) # get plugin name | |
104 | title = re.findall(r"Title: (.+)", e) # get vulnerabilities title | |
105 | self.plugins[name[0]] = title # insert plugin in dicc {'plugin' : ['titles', 'titles']} | |
106 | ||
107 | def addThemesOrPluginsVulns(self, wpscan_db_filename, dic, host_id, serv_id, domain, wp_url, name): | |
108 | db_file_path = self.search_file_in_wpscan_folder(wpscan_db_filename) | |
109 | with open(db_file_path, "r") as data: | |
110 | j = json.load(data) | |
111 | for p in dic: | |
112 | for title in dic[p]: | |
113 | for vuln in j[p]['vulnerabilities']: # iter vulnerabilities | |
114 | if vuln['title'] == title: # if output title is equal | |
115 | title = vuln['title'] # title | |
116 | risk = self.risks[vuln['vuln_type']] # vuln type (xss,rce,lfi,etc) - risk | |
117 | location = wp_url+'wp-content/'+name+'/'+p+'/' | |
118 | if 'url' in vuln['references']: # if references | |
119 | refs = vuln['references']['url'] #references[] | |
120 | else: | |
121 | refs = [] # references null | |
122 | self.createAndAddVulnWebToService( | |
123 | host_id, | |
124 | serv_id, | |
125 | title, | |
126 | severity=risk, | |
127 | website=domain, | |
128 | ref=refs, | |
129 | path=location) | |
130 | ||
131 | def addWPVulns(self, wpscan_db_filename, version, host_id, serv_id, domain): | |
132 | db_file_path = self.search_file_in_wpscan_folder(wpscan_db_filename) | |
133 | with open(db_file_path, "r") as data: | |
134 | j = json.load(data) | |
135 | for vuln in j[version]['vulnerabilities']: # iter vulnerabilities | |
136 | title = vuln['title'] # title | |
137 | risk = self.risks[vuln['vuln_type']] # vuln type (xss,rce,lfi,etc) - risk | |
138 | if 'url' in vuln['references']: # if references | |
139 | refs = vuln['references']['url'] # references[] | |
140 | else: | |
141 | refs = [] # references null | |
142 | self.createAndAddVulnWebToService( | |
143 | host_id, | |
144 | serv_id, | |
145 | title, | |
146 | severity=risk, | |
147 | website=domain, | |
148 | ref=refs) | |
149 | ||
150 | def parseOutputString(self, output, debug=False): | |
151 | """Parses the output given as a string by the wpscan tool and creates | |
152 | the appropiate hosts, service and vulnerabilites. Return | |
153 | nothing. | |
154 | """ | |
155 | self.parseOutputWpscan(output) | |
156 | wp_url = re.search(r"URL: ((http[s]?)\:\/\/([\w\.]+)[.\S]+)", output) | |
157 | service, base_url = self.__get_service_and_url_from_output(output) | |
158 | if service and base_url: | |
159 | port = self.getPort(wp_url.group(1), service) | |
160 | host_ip = socket.gethostbyname_ex(base_url)[2][0] | |
161 | host_id = self.createAndAddHost( | |
162 | host_ip, | |
163 | hostnames=[base_url]) | |
164 | ||
165 | service_id = self.createAndAddServiceToHost( | |
166 | host_id, | |
167 | service, | |
168 | "tcp", | |
169 | ports=[port]) | |
170 | ||
171 | potential_vulns = re.findall(r"(\[\!\].*)", output) | |
172 | for potential_vuln in potential_vulns: | |
173 | vuln_name, severity = self.__get_name_and_severity(potential_vuln) | |
174 | if vuln_name is not None: | |
175 | vuln = potential_vuln # they grow up so fast | |
176 | path = self.__get_path_from_vuln(vuln) | |
177 | self.createAndAddVulnWebToService( | |
178 | host_id, | |
179 | service_id, | |
180 | name=vuln_name, | |
181 | website=base_url, | |
182 | path=path, | |
183 | severity=severity) | |
184 | ||
185 | if len(self.plugins) > 0: | |
186 | self.addThemesOrPluginsVulns( | |
187 | 'plugins.json', | |
188 | self.plugins, | |
189 | host_id, | |
190 | service_id, | |
191 | base_url, | |
192 | wp_url.group(1), | |
193 | 'plugins') | |
194 | ||
195 | if len(self.wpversion) > 0: | |
196 | self.addWPVulns( | |
197 | 'wordpresses.json', | |
198 | self.wpversion, | |
199 | host_id, | |
200 | service_id, | |
201 | base_url) | |
202 | ||
203 | if len(self.themes) > 0: | |
204 | self.addThemesOrPluginsVulns( | |
205 | 'themes.json', | |
206 | self.themes, | |
207 | host_id, | |
208 | service_id, | |
209 | base_url, | |
210 | wp_url.group(1), | |
211 | 'themes') | |
212 | ||
213 | def __get_service_and_url_from_output(self, output): | |
214 | """ Return the service (http or https) and the base URL (URL without | |
215 | protocol) from a given string. In case more than one URL is found, | |
216 | return the service and base_url of the first one, ignore others. | |
217 | """ | |
218 | search_url = re.search(r"URL: ((http[s]?)\:\/\/([\w\.]+)[.\S]+)", output) | |
219 | if not search_url: | |
220 | return None, None | |
221 | else: | |
222 | service, base_url = search_url.group(2), search_url.group(3) | |
223 | return service, base_url | |
224 | ||
225 | def __get_name_and_severity(self, potential_vuln): | |
226 | """Regex the potential_vuln string against a regex with all | |
227 | the vulnerabilities given by WPscan. Returns a regex match object with | |
228 | the vulnerability's name and severity if the regex found something | |
229 | and (None, None) if the regex found nothing. | |
230 | """ | |
231 | critical_search = re.search(r"Website is not fully configured|" | |
232 | "Debug log file found|" | |
233 | "wp-config\.php backup file has been found|" | |
234 | "searchreplacedb2.php has been found", | |
235 | potential_vuln) | |
236 | if critical_search: | |
237 | return critical_search.group(0), "critical" | |
238 | ||
239 | info_search = re.search(r"Directory listing is enabled|" | |
240 | "An error_log file has been found|" | |
241 | "file exists exposing a version number|" | |
242 | "Full Path Disclosure|" | |
243 | "Registration is enabled|" | |
244 | "(Upload|Includes) directory has directory listing enabled|" | |
245 | "Default first Wordpress username 'admin' is still used", | |
246 | potential_vuln) | |
247 | if info_search: | |
248 | return info_search.group(0), "info" | |
249 | ||
250 | return None, None | |
251 | ||
252 | def __get_path_from_vuln(self, vuln): | |
253 | """Given a vuln as string, return the path as a string (empty string | |
254 | for path not found). | |
255 | """ | |
256 | path_search = re.search("(?P<url>https?://[^\s]+)", vuln) | |
257 | path = path_search.group('url') if path_search else "" | |
258 | return path | |
259 | ||
260 | def processCommandString(self, username, current_path, command_string): | |
261 | return None | |
262 | ||
263 | ||
264 | def createPlugin(): | |
265 | return WPScanPlugin() | |
266 | ||
267 | if __name__ == "__main__": | |
268 | import sys | |
269 | import os | |
270 | if len(sys.argv) == 2: | |
271 | report_file = sys.argv[1] | |
272 | if os.path.isfile(report_file): | |
273 | plugin = createPlugin() | |
274 | plugin.processReport(report_file) | |
275 | print(plugin.get_json()) | |
276 | else: | |
277 | print(f"Report not found: {report_file}") | |
278 | else: | |
279 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
280 | ||
281 | # 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 | # 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 PluginXMLFormat | |
7 | import re | |
8 | import os | |
9 | import sys | |
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 | |
18 | ||
19 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] | |
20 | ||
21 | current_path = os.path.abspath(os.getcwd()) | |
22 | ||
23 | __author__ = "Francisco Amato" | |
24 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
25 | __credits__ = ["Francisco Amato"] | |
26 | __license__ = "" | |
27 | __version__ = "1.0.0" | |
28 | __maintainer__ = "Francisco Amato" | |
29 | __email__ = "[email protected]" | |
30 | __status__ = "Development" | |
31 | ||
32 | ||
33 | class X1XmlParser: | |
34 | """ | |
35 | The objective of this class is to parse an xml file generated by the x1 tool. | |
36 | ||
37 | TODO: Handle errors. | |
38 | TODO: Test x1 output version. Handle what happens if the parser doesn't support it. | |
39 | TODO: Test cases. | |
40 | ||
41 | @param x1_xml_filepath A proper xml generated by x1 | |
42 | """ | |
43 | ||
44 | def __init__(self, xml_output): | |
45 | ||
46 | tree = self.parse_xml(xml_output) | |
47 | if tree: | |
48 | self.items = [data for data in self.get_items(tree)] | |
49 | else: | |
50 | self.items = [] | |
51 | ||
52 | def parse_xml(self, xml_output): | |
53 | """ | |
54 | Open and parse an xml file. | |
55 | ||
56 | TODO: Write custom parser to just read the nodes that we need instead of | |
57 | reading the whole file. | |
58 | ||
59 | @return xml_tree An xml tree instance. None if error. | |
60 | """ | |
61 | try: | |
62 | tree = ET.fromstring(xml_output) | |
63 | except SyntaxError as err: | |
64 | print("SyntaxError: %s. %s" % (err, xml_output)) | |
65 | return None | |
66 | ||
67 | return tree | |
68 | ||
69 | def get_items(self, tree): | |
70 | """ | |
71 | @return items A list of Host instances | |
72 | """ | |
73 | ||
74 | for node in tree.findall('results/landscape/system/component'): | |
75 | yield Item(node) | |
76 | ||
77 | ||
78 | class Item: | |
79 | """ | |
80 | An abstract representation of a Item | |
81 | ||
82 | ||
83 | @param item_node A item_node taken from an x1 xml tree | |
84 | """ | |
85 | ||
86 | def __init__(self, item_node): | |
87 | self.node = item_node | |
88 | ||
89 | self.name = self.get_text_from_subnode('name') | |
90 | self.host = self.get_text_from_subnode('host') | |
91 | self.vclass = self.get_text_from_subnode('class') | |
92 | ||
93 | self.connector = self.node.find('connector') | |
94 | self.cname = self.connector.get('name') | |
95 | data = self.cname.split("/") | |
96 | self.port, self.protocol = data[0].split() | |
97 | self.srvname = data[1] | |
98 | ||
99 | self.cresults = self.getResults(self.connector) | |
100 | self.results = self.getResults(self.node) | |
101 | ||
102 | def getResults(self, tree): | |
103 | """ | |
104 | :param tree: | |
105 | """ | |
106 | for self.issues in tree.findall('modResults/moduleResult'): | |
107 | yield Results(self.issues) | |
108 | ||
109 | def get_text_from_subnode(self, subnode_xpath_expr): | |
110 | """ | |
111 | Finds a subnode in the host node and the retrieves a value from it. | |
112 | ||
113 | @return An attribute value | |
114 | """ | |
115 | sub_node = self.node.find(subnode_xpath_expr) | |
116 | if sub_node is not None: | |
117 | return sub_node.text | |
118 | ||
119 | return None | |
120 | ||
121 | ||
122 | class Results(): | |
123 | ||
124 | def __init__(self, issue_node): | |
125 | self.node = issue_node | |
126 | self.id = self.get_text_from_subnode('id') | |
127 | self.name = self.get_text_from_subnode('name') | |
128 | ||
129 | self.category = self.get_text_from_subnode('category') | |
130 | self.trendingStatus = self.get_text_from_subnode('trendingStatus') | |
131 | self.description = self.get_text_from_subnode('description') | |
132 | self.risk = self.get_text_from_subnode('risk') | |
133 | self.resolution = self.get_text_from_subnode('solution') | |
134 | self.ref = [] | |
135 | for r in issue_node.findall('refs/reference'): | |
136 | ||
137 | self.ref.append(r.get('type') + "-" + r.get('text')) | |
138 | ||
139 | def get_text_from_subnode(self, subnode_xpath_expr): | |
140 | """ | |
141 | Finds a subnode in the host node and the retrieves a value from it. | |
142 | ||
143 | @return An attribute value | |
144 | """ | |
145 | sub_node = self.node.find(subnode_xpath_expr) | |
146 | if sub_node is not None: | |
147 | return sub_node.text | |
148 | ||
149 | return None | |
150 | ||
151 | ||
152 | class X1Plugin(PluginXMLFormat): | |
153 | """ | |
154 | Example plugin to parse x1 output. | |
155 | """ | |
156 | ||
157 | def __init__(self): | |
158 | super().__init__() | |
159 | self.identifier_tag = ["session", "landscapePolicy"] | |
160 | self.id = "X1" | |
161 | self.name = "Onapsis X1 XML Output Plugin" | |
162 | self.plugin_version = "0.0.1" | |
163 | self.version = "Onapsis X1 2.56" | |
164 | self.framework_version = "1.0.0" | |
165 | self.options = None | |
166 | self._current_output = None | |
167 | self._command_regex = re.compile(r'^(sudo x1|\.\/x1).*?') | |
168 | ||
169 | ||
170 | ||
171 | def parseOutputString(self, output, debug=False): | |
172 | ||
173 | parser = X1XmlParser(output) | |
174 | for item in parser.items: | |
175 | h_id = self.createAndAddHost(item.host, item.name) | |
176 | i_id = self.createAndAddInterface( | |
177 | h_id, item.host, ipv4_address=item.host, hostname_resolution=[item.vclass]) | |
178 | s_id = self.createAndAddServiceToInterface(h_id, i_id, item.srvname, | |
179 | item.protocol, | |
180 | ports=[str(item.port)], | |
181 | status="open") | |
182 | for v in item.results: | |
183 | desc = v.description | |
184 | v_id = self.createAndAddVulnToService(h_id, s_id, v.name, desc=desc, | |
185 | ref=v.ref, severity=v.risk, resolution=v.resolution) | |
186 | ||
187 | for v in item.cresults: | |
188 | desc = v.description | |
189 | v_id = self.createAndAddVulnToService(h_id, s_id, v.name, desc=desc, | |
190 | ref=v.ref, severity=v.risk, resolution=v.resolution) | |
191 | ||
192 | del parser | |
193 | ||
194 | def processCommandString(self, username, current_path, command_string): | |
195 | return None | |
196 | ||
197 | def setHost(self): | |
198 | pass | |
199 | ||
200 | ||
201 | def createPlugin(): | |
202 | return X1Plugin() | |
203 | ||
204 | if __name__ == "__main__": | |
205 | import sys | |
206 | import os | |
207 | if len(sys.argv) == 2: | |
208 | report_file = sys.argv[1] | |
209 | if os.path.isfile(report_file): | |
210 | plugin = createPlugin() | |
211 | plugin.processReport(report_file) | |
212 | print(plugin.get_json()) | |
213 | else: | |
214 | print(f"Report not found: {report_file}") | |
215 | else: | |
216 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
217 | ||
218 | # 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 | # I'm Py3⏎ |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2017 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | """ | |
5 | import re | |
6 | import socket | |
7 | from faraday_plugins.plugins.plugin import PluginBase | |
8 | ||
9 | __author__ = "Roberto Focke" | |
10 | __copyright__ = "Copyright (c) 2017, Infobyte LLC" | |
11 | __license__ = "" | |
12 | __version__ = "1.0.0" | |
13 | ||
14 | ||
15 | class xsssniper(PluginBase): | |
16 | ||
17 | def __init__(self): | |
18 | super().__init__() | |
19 | self.id = "xsssniper" | |
20 | self.name = "xsssniper" | |
21 | self.plugin_version = "0.0.1" | |
22 | 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 xsssniper\.py|.\/xsssniper\.py|python xsssniper\.py)') | |
25 | ||
26 | def parseOutputString(self, output, debug=False): | |
27 | parametro = [] | |
28 | lineas = output.split("\n") | |
29 | aux = 0 | |
30 | for linea in lineas: | |
31 | if not linea: | |
32 | continue | |
33 | linea = linea.lower() | |
34 | if ((linea.find("target:")>0)): | |
35 | url = re.findall('(?:[-\w.]|(?:%[\da-fA-F]{2}))+', linea) | |
36 | host_id = self.createAndAddHost(url[3]) | |
37 | address=socket.gethostbyname(url[3]) | |
38 | interface_id = self.createAndAddInterface(host_id,address,ipv4_address=address,hostname_resolution=url[3]) | |
39 | if ((linea.find("method")>0)): | |
40 | list_a = re.findall("\w+", linea) | |
41 | metodo= list_a[1] | |
42 | if ((linea.find("query string:")>0)): | |
43 | lista_parametros=linea.split('=') | |
44 | aux=len(lista_parametros) | |
45 | if ((linea.find("param:")>0)): | |
46 | list2 = re.findall("\w+",linea) | |
47 | parametro.append(list2[1]) | |
48 | service_id = self.createAndAddServiceToInterface(host_id, interface_id, self.protocol, 'tcp', | |
49 | ports=['80'], status='Open', version="", description="") | |
50 | if aux != 0: | |
51 | self.createAndAddVulnWebToService(host_id, service_id, name="xss", desc="XSS", ref='', severity='med', | |
52 | website=url[0], path='', method=metodo, pname='', | |
53 | params=''.join(parametro), request='', response='') | |
54 | ||
55 | def processCommandString(self, username, current_path, command_string): | |
56 | return None | |
57 | ||
58 | ||
59 | def createPlugin(): | |
60 | return xsssniper() | |
61 | ||
62 | if __name__ == "__main__": | |
63 | import sys | |
64 | import os | |
65 | if len(sys.argv) == 2: | |
66 | report_file = sys.argv[1] | |
67 | if os.path.isfile(report_file): | |
68 | plugin = createPlugin() | |
69 | plugin.processReport(report_file) | |
70 | print(plugin.get_json()) | |
71 | else: | |
72 | print(f"Report not found: {report_file}") | |
73 | else: | |
74 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
75 | ||
76 | # 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 | /* | |
1 | * Zed Attack Proxy (ZAP) and its related class files. | |
2 | * | |
3 | * ZAP is an HTTP/HTTPS proxy for assessing web application security. | |
4 | * | |
5 | * Copyright 2018 The ZAP Development Team | |
6 | * | |
7 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
8 | * you may not use this file except in compliance with the License. | |
9 | * You may obtain a copy of the License at | |
10 | * | |
11 | * http://www.apache.org/licenses/LICENSE-2.0 | |
12 | * | |
13 | * Unless required by applicable law or agreed to in writing, software | |
14 | * distributed under the License is distributed on an "AS IS" BASIS, | |
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
16 | * See the License for the specific language governing permissions and | |
17 | * limitations under the License. | |
18 | */ | |
19 | ||
20 | package org.zaproxy.zap.extension.faraday; | |
21 | ||
22 | import org.parosproxy.paros.Constant; | |
23 | ||
24 | import javax.swing.*; | |
25 | import java.io.*; | |
26 | import java.util.Properties; | |
27 | ||
28 | public class Configuration { | |
29 | private String server; | |
30 | private String user; | |
31 | private String password; | |
32 | private String session; | |
33 | private String workspace; | |
34 | private boolean autoImport; | |
35 | private static Configuration _instance; | |
36 | ||
37 | private Configuration() { | |
38 | this.user = ""; | |
39 | this.password = ""; | |
40 | this.server = "http://127.0.0.1:5985/"; | |
41 | this.autoImport = false; | |
42 | } | |
43 | ||
44 | public static Configuration getSingleton() { | |
45 | if (_instance == null) | |
46 | _instance = new Configuration(); | |
47 | return _instance; | |
48 | } | |
49 | ||
50 | public boolean save() throws IOException { | |
51 | ||
52 | Properties prop = new Properties(); | |
53 | OutputStream output = null; | |
54 | ||
55 | String userHome = System.getProperty("user.home"); | |
56 | String outputFolder = Constant.getZapHome() + "faraday"; | |
57 | File folder = new File(outputFolder); | |
58 | if (!folder.exists()) { | |
59 | folder.mkdir(); | |
60 | } | |
61 | ||
62 | ||
63 | String filePath = outputFolder + File.separator + this.getUser() + ".properties"; | |
64 | output = new FileOutputStream(filePath); | |
65 | ||
66 | // set the properties value | |
67 | prop.setProperty("fuser", this.getUser()); | |
68 | prop.setProperty("fpassword", this.getPassword()); | |
69 | prop.setProperty("fserver", this.getServer()); | |
70 | prop.setProperty("fworkspace", this.getWorkspace()); | |
71 | prop.setProperty("fsession", this.getSession()); | |
72 | ||
73 | // save properties to project root folder | |
74 | prop.store(output, null); | |
75 | ||
76 | if (output != null) { | |
77 | try { | |
78 | output.close(); | |
79 | } catch (IOException e) { | |
80 | e.printStackTrace(); | |
81 | return false; | |
82 | } | |
83 | } | |
84 | ||
85 | return true; | |
86 | } | |
87 | ||
88 | ||
89 | public void restore(String fUser) throws IOException { | |
90 | Properties prop = new Properties(); | |
91 | InputStream input = null; | |
92 | ||
93 | String outputFolder = Constant.getZapHome() + "faraday"; | |
94 | String filePath = outputFolder + File.separator + fUser + ".properties"; | |
95 | input = new FileInputStream(filePath); | |
96 | ||
97 | // load a properties file | |
98 | prop.load(input); | |
99 | ||
100 | this.setUser(prop.getProperty("fuser")); | |
101 | this.setPassword(prop.getProperty("fpassword")); | |
102 | this.setServer(prop.getProperty("fserver")); | |
103 | this.setWorkspace(prop.getProperty("fworkspace")); | |
104 | ||
105 | if (input != null) { | |
106 | try { | |
107 | input.close(); | |
108 | } catch (IOException e) { | |
109 | e.printStackTrace(); | |
110 | } | |
111 | } | |
112 | ||
113 | } | |
114 | ||
115 | public String getUser() { | |
116 | return user; | |
117 | } | |
118 | ||
119 | public void setUser(String user) { | |
120 | this.user = user; | |
121 | } | |
122 | ||
123 | public String getPassword() { | |
124 | return password; | |
125 | } | |
126 | ||
127 | public void setPassword(String password) { | |
128 | this.password = password; | |
129 | } | |
130 | ||
131 | public String getServer() { | |
132 | return server; | |
133 | } | |
134 | ||
135 | public void setServer(String server) { | |
136 | this.server = server; | |
137 | } | |
138 | ||
139 | public boolean isAutoImport() { | |
140 | return autoImport; | |
141 | } | |
142 | ||
143 | public void setAutoImport(boolean autoImport) { | |
144 | this.autoImport = autoImport; | |
145 | } | |
146 | ||
147 | public String getSession() { | |
148 | return session; | |
149 | } | |
150 | ||
151 | public void setSession(String session) { | |
152 | this.session = session; | |
153 | } | |
154 | ||
155 | public String getWorkspace() { | |
156 | return workspace; | |
157 | } | |
158 | ||
159 | public void setWorkspace(String workspace) { | |
160 | this.workspace = workspace; | |
161 | } | |
162 | } |
0 | package org.zaproxy.zap.extension.faraday; | |
1 | ||
2 | ||
3 | import org.apache.log4j.Logger; | |
4 | import org.parosproxy.paros.Constant; | |
5 | ||
6 | import javax.swing.*; | |
7 | import javax.swing.border.Border; | |
8 | import java.awt.*; | |
9 | import java.awt.event.*; | |
10 | import java.io.*; | |
11 | import java.util.ArrayList; | |
12 | import java.util.Properties; | |
13 | import java.util.ResourceBundle; | |
14 | ||
15 | public class ConfigurationDialog extends JFrame { | |
16 | private static final Logger logger = Logger.getLogger(ConfigurationDialog.class); | |
17 | private ResourceBundle messages = null; | |
18 | private FaradayClient faradayClient; | |
19 | ||
20 | private static String LOGIN_BUTTON = "Login"; | |
21 | private static String LOGOUT_BUTTON = "Logout"; | |
22 | private static String WORKSPACES_FIELD = "Select faraday workspace"; | |
23 | private static String IMPORT_NEW_VULNS_FIELD = "Import new vulnerabilities"; | |
24 | private static String SET_CONFIG_AS_DEFAULT = "Set this configuration as default"; | |
25 | private static String IMPORT_BUTTON = "Import vulnerabilities"; | |
26 | private static String REFRESH_BUTTON = "Refresh"; | |
27 | private static String RESTORE_BUTTON = "Restore"; | |
28 | private static String SAVE_BUTTON = "Save"; | |
29 | ||
30 | private JTabbedPane tabbedPane; | |
31 | private JPanel authPanel; | |
32 | private JPanel configPanel; | |
33 | ||
34 | private JTextField fldUser; | |
35 | private JTextField fldPass; | |
36 | private JTextField fldServer; | |
37 | ||
38 | private JComboBox cmbWorkspaces; | |
39 | private JCheckBox cboxSetConfigDefault; | |
40 | ||
41 | ||
42 | private JButton loginButton; | |
43 | private JButton logoutButton; | |
44 | private JButton refreshButton; | |
45 | private JButton restoreButton; | |
46 | private JButton importButton; | |
47 | private JButton saveButton; | |
48 | private JButton closeButton; | |
49 | ||
50 | ||
51 | public ConfigurationDialog(String s) throws HeadlessException { | |
52 | super(s); | |
53 | } | |
54 | ||
55 | ||
56 | public void init() { | |
57 | logger.debug("Init Faraday configuration dialog"); | |
58 | messages = ResourceBundle.getBundle( | |
59 | this.getClass().getPackage().getName() + | |
60 | ".Messages", Constant.getLocale()); | |
61 | // Setup the content-pane of JFrame in BorderLayout | |
62 | Container cp = this.getContentPane(); | |
63 | cp.setLayout(new BorderLayout(5, 5)); | |
64 | Border padding = BorderFactory.createEmptyBorder(10, 10, 10, 10); | |
65 | ||
66 | ||
67 | String USERNAME_FIELD = messages.getString("faraday.config.dialog.auth.user"); | |
68 | String PASS_FIELD = messages.getString("faraday.config.dialog.auth.pass"); | |
69 | String SERVER_FIELD = messages.getString("faraday.config.dialog.server"); | |
70 | LOGIN_BUTTON = messages.getString("faraday.config.dialog.auth.login"); | |
71 | LOGOUT_BUTTON = messages.getString("faraday.config.dialog.auth.logout"); | |
72 | WORKSPACES_FIELD = messages.getString("faraday.config.dialog.workspace"); | |
73 | IMPORT_NEW_VULNS_FIELD = messages.getString("faraday.config.dialog.import.new"); | |
74 | SET_CONFIG_AS_DEFAULT = messages.getString("faraday.config.dialog.default"); | |
75 | IMPORT_BUTTON = messages.getString("faraday.config.dialog.import.new"); | |
76 | REFRESH_BUTTON = messages.getString("faraday.config.dialog.refresh"); | |
77 | RESTORE_BUTTON = messages.getString("faraday.config.dialog.restore"); | |
78 | SAVE_BUTTON = messages.getString("faraday.config.dialog.save"); | |
79 | tabbedPane = new JTabbedPane(); | |
80 | ||
81 | JPanel buttonLoginPanel = new JPanel(); | |
82 | buttonLoginPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); | |
83 | ||
84 | JPanel buttonConfigPanel = new JPanel(); | |
85 | buttonConfigPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); | |
86 | ||
87 | authPanel = new JPanel(new GridLayout(4, 2, 10, 2)); | |
88 | authPanel.setBorder(padding); | |
89 | configPanel = new JPanel(new GridLayout(3, 2, 10, 2)); | |
90 | configPanel.setBorder(padding); | |
91 | ||
92 | ||
93 | Configuration configuration = Configuration.getSingleton(); | |
94 | faradayClient = new FaradayClient(configuration.getServer()); | |
95 | ||
96 | authPanel.add(new JLabel(USERNAME_FIELD)); | |
97 | fldUser = new JTextField(10); | |
98 | authPanel.add(fldUser); | |
99 | ||
100 | authPanel.add(new JLabel(PASS_FIELD)); | |
101 | fldPass = new JPasswordField(10); | |
102 | authPanel.add(fldPass); | |
103 | ||
104 | authPanel.add(new JLabel(SERVER_FIELD)); | |
105 | fldServer = new JTextField(10); | |
106 | fldServer.setText(configuration.getServer()); | |
107 | authPanel.add(fldServer); | |
108 | ||
109 | configPanel.add(getCBoxSetDefaultConfig()); | |
110 | ||
111 | buttonConfigPanel.add(getCloseButton()); | |
112 | buttonConfigPanel.add(getCloseButton()); | |
113 | buttonConfigPanel.add(getRefreshButton()); | |
114 | buttonConfigPanel.add(getRestoreButton()); | |
115 | buttonConfigPanel.add(getSaveButton()); | |
116 | // buttonConfigPanel.add(getImportButton()); | |
117 | buttonConfigPanel.add(getLoginButton()); | |
118 | buttonConfigPanel.add(getLogoutButton()); | |
119 | ||
120 | ||
121 | authPanel.addComponentListener(new ComponentListener() { | |
122 | @Override | |
123 | public void componentResized(ComponentEvent componentEvent) { | |
124 | ||
125 | } | |
126 | ||
127 | @Override | |
128 | public void componentMoved(ComponentEvent componentEvent) { | |
129 | ||
130 | } | |
131 | ||
132 | @Override | |
133 | public void componentShown(ComponentEvent componentEvent) { | |
134 | ||
135 | refreshButton.setVisible(false); | |
136 | restoreButton.setVisible(false); | |
137 | // importButton.setVisible(false); | |
138 | saveButton.setVisible(false); | |
139 | } | |
140 | ||
141 | @Override | |
142 | public void componentHidden(ComponentEvent componentEvent) { | |
143 | refreshButton.setVisible(true); | |
144 | restoreButton.setVisible(true); | |
145 | // importButton.setVisible(true); | |
146 | saveButton.setVisible(true); | |
147 | } | |
148 | }); | |
149 | ||
150 | configPanel.addComponentListener(new ComponentListener() { | |
151 | @Override | |
152 | public void componentResized(ComponentEvent componentEvent) { | |
153 | ||
154 | } | |
155 | ||
156 | @Override | |
157 | public void componentMoved(ComponentEvent componentEvent) { | |
158 | ||
159 | } | |
160 | ||
161 | @Override | |
162 | public void componentShown(ComponentEvent componentEvent) { | |
163 | loginButton.setVisible(false); | |
164 | logoutButton.setVisible(false); | |
165 | } | |
166 | ||
167 | @Override | |
168 | public void componentHidden(ComponentEvent componentEvent) { | |
169 | if (configuration.getSession().equals("")) { | |
170 | loginButton.setVisible(true); | |
171 | } else { | |
172 | logoutButton.setVisible(true); | |
173 | } | |
174 | } | |
175 | }); | |
176 | ||
177 | tabbedPane.addTab(messages.getString("faraday.config.dialog.tab.auth"), null, authPanel, null); | |
178 | tabbedPane.setMnemonicAt(0, KeyEvent.VK_1); | |
179 | ||
180 | ||
181 | tabbedPane.addTab(messages.getString("faraday.config.dialog.tabs.conf"), null, configPanel, null); | |
182 | tabbedPane.setMnemonicAt(1, KeyEvent.VK_2); | |
183 | ||
184 | tabbedPane.setEnabledAt(1, false); | |
185 | ||
186 | cp.add(tabbedPane, BorderLayout.NORTH); | |
187 | cp.add(buttonConfigPanel, BorderLayout.SOUTH); | |
188 | ||
189 | if (configuration.getSession() != null && !configuration.getSession().equals("")) { | |
190 | logoutButton.setVisible(true); | |
191 | loginButton.setVisible(false); | |
192 | } else { | |
193 | loginButton.setVisible(true); | |
194 | logoutButton.setVisible(false); | |
195 | } | |
196 | ||
197 | ||
198 | if (!configuration.getUser().equals("") && !configuration.getPassword().equals("")) { | |
199 | if (faradayClient.Login(configuration.getUser(), configuration.getPassword(), configuration.getServer())) { | |
200 | fldUser.setText(configuration.getUser()); | |
201 | fldPass.setText(configuration.getPassword()); | |
202 | fldServer.setText(configuration.getServer()); | |
203 | ||
204 | tabbedPane.setEnabledAt(1, true); | |
205 | tabbedPane.setSelectedIndex(1); | |
206 | ||
207 | cboxSetConfigDefault.setSelected(true); | |
208 | ||
209 | if (cmbWorkspaces == null) { | |
210 | configPanel.add(new JLabel(WORKSPACES_FIELD)); | |
211 | configPanel.add(getWSComboBox()); | |
212 | } | |
213 | } | |
214 | } | |
215 | ||
216 | this.setSize(550, 300); | |
217 | this.setResizable(false); | |
218 | this.setLocationRelativeTo(null); | |
219 | this.setVisible(true); | |
220 | } | |
221 | ||
222 | ||
223 | private JButton getLoginButton() { | |
224 | if (this.loginButton == null) { | |
225 | this.loginButton = new JButton(); | |
226 | this.loginButton.setText(LOGIN_BUTTON); | |
227 | this.loginButton.addActionListener(new ActionListener() { | |
228 | public void actionPerformed(ActionEvent e) { | |
229 | if (fldUser.getText().equals("") || fldPass.getText().equals("") || fldServer.getText().equals("")) { | |
230 | showMessage(messages.getString("faraday.message.invalid.check.credentials"), messages.getString("faraday.dialog.login.title"), JOptionPane.ERROR_MESSAGE); | |
231 | } else { | |
232 | if (faradayClient.Login(fldUser.getText(), fldPass.getText(), fldServer.getText())) { | |
233 | logoutButton.setVisible(true); | |
234 | loginButton.setVisible(false); | |
235 | if (!tabbedPane.isEnabledAt(1)) { | |
236 | tabbedPane.setEnabledAt(1, true); | |
237 | } | |
238 | tabbedPane.setSelectedIndex(1); | |
239 | if (cmbWorkspaces == null) { | |
240 | configPanel.add(new JLabel(WORKSPACES_FIELD)); | |
241 | configPanel.add(getWSComboBox()); | |
242 | } else { | |
243 | configPanel.remove(cmbWorkspaces); | |
244 | configPanel.add(getWSComboBox()); | |
245 | } | |
246 | } else { | |
247 | showMessage(messages.getString("faraday.message.invalid.credentials"), messages.getString("faraday.dialog.login.title"), JOptionPane.ERROR_MESSAGE); | |
248 | } | |
249 | } | |
250 | ||
251 | ||
252 | } | |
253 | }); | |
254 | ||
255 | ||
256 | } | |
257 | ||
258 | return this.loginButton; | |
259 | } | |
260 | ||
261 | ||
262 | private JButton getLogoutButton() { | |
263 | if (this.logoutButton == null) { | |
264 | this.logoutButton = new JButton(); | |
265 | this.logoutButton.setText(LOGOUT_BUTTON); | |
266 | this.logoutButton.addActionListener(new ActionListener() { | |
267 | public void actionPerformed(ActionEvent e) { | |
268 | Configuration configuration = Configuration.getSingleton(); | |
269 | String userTemp = configuration.getUser(); | |
270 | if (faradayClient.Logout()) { | |
271 | logoutButton.setVisible(false); | |
272 | loginButton.setVisible(true); | |
273 | ||
274 | if (tabbedPane.isEnabledAt(1)) { | |
275 | tabbedPane.setEnabledAt(1, false); | |
276 | } | |
277 | tabbedPane.setSelectedIndex(0); | |
278 | ||
279 | Properties prop = new Properties(); | |
280 | InputStream input = null; | |
281 | try { | |
282 | String filePath = Constant.getZapHome() + "faraday" + File.separator + "default.properties"; | |
283 | input = new FileInputStream(filePath); | |
284 | // load a properties file | |
285 | prop.load(input); | |
286 | // set the properties value | |
287 | String fUser = prop.getProperty("default"); | |
288 | if (fUser.equals(userTemp)) { | |
289 | removeDefaultConfig(); | |
290 | } | |
291 | ||
292 | } catch (IOException io) { | |
293 | System.out.println("We can't found default.properties file"); | |
294 | } finally { | |
295 | if (input != null) { | |
296 | try { | |
297 | input.close(); | |
298 | } catch (IOException er) { | |
299 | er.printStackTrace(); | |
300 | } | |
301 | } | |
302 | } | |
303 | ||
304 | ||
305 | showMessage(messages.getString("faraday.dialog.logout.success"), messages.getString("faraday.dialog.logout.title"), JOptionPane.INFORMATION_MESSAGE); | |
306 | } else { | |
307 | showMessage(messages.getString("faraday.dialog.logout.error"), messages.getString("faraday.dialog.logout.title"), JOptionPane.ERROR_MESSAGE); | |
308 | } | |
309 | } | |
310 | }); | |
311 | } | |
312 | ||
313 | return this.logoutButton; | |
314 | } | |
315 | ||
316 | ||
317 | private JButton getRefreshButton() { | |
318 | if (this.refreshButton == null) { | |
319 | this.refreshButton = new JButton(); | |
320 | this.refreshButton.setText(REFRESH_BUTTON); | |
321 | this.refreshButton.addActionListener(new ActionListener() { | |
322 | public void actionPerformed(ActionEvent e) { | |
323 | refreshWorkspaces(true); | |
324 | } | |
325 | }); | |
326 | } | |
327 | ||
328 | return this.refreshButton; | |
329 | } | |
330 | ||
331 | ||
332 | private JButton getCloseButton() { | |
333 | if (this.closeButton == null) { | |
334 | this.closeButton = new JButton(); | |
335 | this.closeButton.setText(messages.getString("faraday.dialog.button.close")); | |
336 | this.closeButton.addActionListener(new ActionListener() { | |
337 | public void actionPerformed(ActionEvent e) { | |
338 | setVisible(false); | |
339 | dispose(); | |
340 | } | |
341 | }); | |
342 | } | |
343 | ||
344 | return this.closeButton; | |
345 | } | |
346 | ||
347 | ||
348 | private JButton getRestoreButton() { | |
349 | if (this.restoreButton == null) { | |
350 | this.restoreButton = new JButton(); | |
351 | this.restoreButton.setText(RESTORE_BUTTON); | |
352 | this.restoreButton.addActionListener(new ActionListener() { | |
353 | public void actionPerformed(ActionEvent e) { | |
354 | String fUser = JOptionPane.showInputDialog(messages.getString("faraday.config.dialog.restore"), messages.getString("faraday.dialog.enter.user")); | |
355 | if (fUser != null) { | |
356 | restoreConfiguration(fUser); | |
357 | } | |
358 | } | |
359 | }); | |
360 | } | |
361 | ||
362 | return this.restoreButton; | |
363 | } | |
364 | ||
365 | ||
366 | private JButton getImportButton() { | |
367 | if (this.importButton == null) { | |
368 | this.importButton = new JButton(); | |
369 | this.importButton.setText(IMPORT_BUTTON); | |
370 | this.importButton.addActionListener(new ActionListener() { | |
371 | public void actionPerformed(ActionEvent e) { | |
372 | ||
373 | } | |
374 | }); | |
375 | } | |
376 | ||
377 | return this.importButton; | |
378 | } | |
379 | ||
380 | ||
381 | private JButton getSaveButton() { | |
382 | if (this.saveButton == null) { | |
383 | this.saveButton = new JButton(); | |
384 | this.saveButton.setText(SAVE_BUTTON); | |
385 | this.saveButton.addActionListener(new ActionListener() { | |
386 | public void actionPerformed(ActionEvent e) { | |
387 | saveConfiguration(); | |
388 | } | |
389 | }); | |
390 | } | |
391 | ||
392 | return this.saveButton; | |
393 | } | |
394 | ||
395 | ||
396 | private JComboBox getWSComboBox() { | |
397 | Configuration configuration = Configuration.getSingleton(); | |
398 | ||
399 | ArrayList<String> wsList = faradayClient.GetWorkspaces(); | |
400 | String[] workspaces = new String[wsList.size()]; | |
401 | for (int i = 0; i < wsList.size(); i++) { | |
402 | workspaces[i] = wsList.get(i); | |
403 | } | |
404 | cmbWorkspaces = new JComboBox(workspaces); | |
405 | if (workspaces.length > 0) { | |
406 | if (configuration.getWorkspace() != null) { | |
407 | cmbWorkspaces.setSelectedItem(configuration.getWorkspace()); | |
408 | } else { | |
409 | configuration.setWorkspace(workspaces[0]); | |
410 | } | |
411 | } | |
412 | cmbWorkspaces.addActionListener(new ActionListener() { | |
413 | @Override | |
414 | public void actionPerformed(ActionEvent actionEvent) { | |
415 | Configuration.getSingleton().setWorkspace(cmbWorkspaces.getSelectedItem().toString()); | |
416 | } | |
417 | }); | |
418 | ||
419 | ||
420 | return cmbWorkspaces; | |
421 | } | |
422 | ||
423 | ||
424 | private JCheckBox getCBoxSetDefaultConfig() { | |
425 | if (this.cboxSetConfigDefault == null) { | |
426 | cboxSetConfigDefault = new JCheckBox(SET_CONFIG_AS_DEFAULT, false); | |
427 | ||
428 | cboxSetConfigDefault.addActionListener(new ActionListener() { | |
429 | @Override | |
430 | public void actionPerformed(ActionEvent actionEvent) { | |
431 | if (cboxSetConfigDefault.isSelected()) { | |
432 | setConfigAsDefault(); | |
433 | } else { | |
434 | removeDefaultConfig(); | |
435 | } | |
436 | } | |
437 | }); | |
438 | } | |
439 | ||
440 | return cboxSetConfigDefault; | |
441 | } | |
442 | ||
443 | ||
444 | private void showMessage(String message, String title, int icon) { | |
445 | JOptionPane.showMessageDialog( | |
446 | this, | |
447 | message, | |
448 | title, | |
449 | icon); | |
450 | } | |
451 | ||
452 | ||
453 | private void saveConfiguration() { | |
454 | try { | |
455 | if (Configuration.getSingleton().save()) { | |
456 | JOptionPane.showMessageDialog( | |
457 | this, | |
458 | messages.getString("faraday.save.config.success"), | |
459 | messages.getString("faraday.config.dialog.title"), | |
460 | JOptionPane.INFORMATION_MESSAGE); | |
461 | } else { | |
462 | JOptionPane.showMessageDialog( | |
463 | this, | |
464 | messages.getString("faraday.save.config.error"), | |
465 | messages.getString("faraday.config.dialog.title"), | |
466 | JOptionPane.ERROR_MESSAGE); | |
467 | ||
468 | } | |
469 | } catch (IOException io) { | |
470 | JOptionPane.showMessageDialog( | |
471 | this, | |
472 | messages.getString("faraday.save.config.error"), | |
473 | messages.getString("faraday.config.dialog.title"), | |
474 | JOptionPane.ERROR_MESSAGE); | |
475 | io.printStackTrace(); | |
476 | ||
477 | } | |
478 | } | |
479 | ||
480 | ||
481 | private void restoreConfiguration(String fUser) { | |
482 | try { | |
483 | Configuration configuration = Configuration.getSingleton(); | |
484 | configuration.restore(fUser); | |
485 | if (faradayClient.Login(configuration.getUser(), configuration.getPassword(), configuration.getServer())) { | |
486 | fldUser.setText(configuration.getUser()); | |
487 | fldPass.setText(configuration.getPassword()); | |
488 | fldServer.setText(configuration.getServer()); | |
489 | ||
490 | tabbedPane.setEnabledAt(1, true); | |
491 | tabbedPane.setSelectedIndex(0); | |
492 | ||
493 | cboxSetConfigDefault.setSelected(false); | |
494 | refreshWorkspaces(false); | |
495 | } else { | |
496 | JOptionPane.showMessageDialog( | |
497 | this, | |
498 | messages.getString("faraday.restore.config.error.login"), | |
499 | messages.getString("faraday.config.dialog.title"), | |
500 | JOptionPane.ERROR_MESSAGE); | |
501 | } | |
502 | } catch (IOException ex) { | |
503 | JOptionPane.showMessageDialog( | |
504 | this, | |
505 | messages.getString("faraday.restore.config.error"), | |
506 | messages.getString("faraday.config.dialog.title"), | |
507 | JOptionPane.ERROR_MESSAGE); | |
508 | } | |
509 | ||
510 | } | |
511 | ||
512 | ||
513 | private void setConfigAsDefault() { | |
514 | Configuration configuration = Configuration.getSingleton(); | |
515 | ||
516 | Properties prop = new Properties(); | |
517 | OutputStream output = null; | |
518 | ||
519 | try { | |
520 | String outputFolder = Constant.getZapHome() + "faraday"; | |
521 | File folder = new File(outputFolder); | |
522 | if (!folder.exists()) { | |
523 | folder.mkdir(); | |
524 | } | |
525 | ||
526 | String filePath = outputFolder + File.separator + "default.properties"; | |
527 | output = new FileOutputStream(filePath); | |
528 | ||
529 | // set the properties value | |
530 | prop.setProperty("default", configuration.getUser()); | |
531 | ||
532 | // save properties to project root folder | |
533 | prop.store(output, null); | |
534 | ||
535 | } catch (IOException io) { | |
536 | JOptionPane.showMessageDialog( | |
537 | this, | |
538 | messages.getString("faraday.set.default.config.error"), | |
539 | messages.getString("faraday.config.dialog.title"), | |
540 | JOptionPane.ERROR_MESSAGE); | |
541 | io.printStackTrace(); | |
542 | } finally { | |
543 | if (output != null) { | |
544 | try { | |
545 | output.close(); | |
546 | } catch (IOException e) { | |
547 | e.printStackTrace(); | |
548 | } | |
549 | } | |
550 | ||
551 | } | |
552 | } | |
553 | ||
554 | ||
555 | private void removeDefaultConfig() { | |
556 | try { | |
557 | ||
558 | String filePath = Constant.getZapHome() + "faraday" + File.separator + "default.properties"; | |
559 | File file = new File(filePath); | |
560 | if (file.delete()) { | |
561 | System.out.println(file.getName() + " is deleted!"); | |
562 | } else { | |
563 | System.out.println("Delete operation is failed."); | |
564 | } | |
565 | ||
566 | } catch (Exception e) { | |
567 | ||
568 | e.printStackTrace(); | |
569 | ||
570 | } | |
571 | } | |
572 | ||
573 | ||
574 | private void refreshWorkspaces(boolean canShowAlert) { | |
575 | if (cmbWorkspaces != null) { | |
576 | configPanel.remove(cmbWorkspaces); | |
577 | configPanel.add(getWSComboBox()); | |
578 | if (canShowAlert) { | |
579 | JOptionPane.showMessageDialog( | |
580 | this, | |
581 | messages.getString("faraday.refresh.workspace.done"), | |
582 | messages.getString("faraday.config.dialog.title"), | |
583 | JOptionPane.INFORMATION_MESSAGE); | |
584 | } | |
585 | } | |
586 | } | |
587 | ||
588 | } |
0 | package org.zaproxy.zap.extension.faraday; | |
1 | ||
2 | import net.sf.json.JSONArray; | |
3 | import net.sf.json.JSONObject; | |
4 | import org.apache.commons.httpclient.URIException; | |
5 | import org.apache.commons.httpclient.methods.PostMethod; | |
6 | import org.apache.http.HttpEntity; | |
7 | import org.apache.http.HttpResponse; | |
8 | ||
9 | import org.apache.http.client.ClientProtocolException; | |
10 | import org.apache.http.client.HttpClient; | |
11 | import org.apache.http.entity.StringEntity; | |
12 | import org.apache.http.client.entity.UrlEncodedFormEntity; | |
13 | import org.apache.http.client.methods.HttpGet; | |
14 | import org.apache.http.client.methods.HttpPost; | |
15 | import org.apache.http.impl.client.HttpClients; | |
16 | import org.apache.http.NameValuePair; | |
17 | import org.apache.http.message.BasicNameValuePair; | |
18 | import org.apache.http.util.EntityUtils; | |
19 | import org.parosproxy.paros.Constant; | |
20 | import org.parosproxy.paros.core.scanner.Alert; | |
21 | import org.parosproxy.paros.model.HistoryReference; | |
22 | ||
23 | import java.io.*; | |
24 | import java.net.*; | |
25 | import java.nio.charset.StandardCharsets; | |
26 | import java.sql.Time; | |
27 | import java.time.Instant; | |
28 | import java.util.*; | |
29 | ||
30 | ||
31 | public class FaradayClient { | |
32 | ||
33 | private String baseUrl; | |
34 | private ResourceBundle messages = null; | |
35 | ||
36 | public FaradayClient(String baseUrl) { | |
37 | this.baseUrl = baseUrl; | |
38 | messages = ResourceBundle.getBundle( | |
39 | this.getClass().getPackage().getName() + | |
40 | ".Messages", Constant.getLocale()); | |
41 | ||
42 | } | |
43 | ||
44 | public boolean Login(String username, String password, String server) { | |
45 | Logout(); | |
46 | HttpClient httpClient = HttpClients.createDefault(); | |
47 | String LOGIN_URL = "_api/login"; | |
48 | HttpPost httpPost = new HttpPost(server + LOGIN_URL); | |
49 | ||
50 | // Request parameters and other properties. | |
51 | List<BasicNameValuePair> params = new ArrayList<>(2); | |
52 | params.add(new BasicNameValuePair("email", username)); | |
53 | params.add(new BasicNameValuePair("password", password)); | |
54 | ||
55 | try { | |
56 | httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8")); | |
57 | HttpResponse response = httpClient.execute(httpPost); | |
58 | if (response.getFirstHeader("Set-Cookie") != null) { | |
59 | Configuration configuration = Configuration.getSingleton(); | |
60 | configuration.setSession(response.getFirstHeader("Set-Cookie").getValue()); | |
61 | configuration.setUser(username); | |
62 | configuration.setPassword(password); | |
63 | configuration.setServer(server); | |
64 | setBaseUrl(server); | |
65 | return true; | |
66 | } else if (response.getStatusLine().getStatusCode() == 302) { | |
67 | return true; | |
68 | } | |
69 | return false; | |
70 | } catch (UnsupportedEncodingException e) { | |
71 | // writing error to Log | |
72 | e.printStackTrace(); | |
73 | return false; | |
74 | } catch (ClientProtocolException e) { | |
75 | e.printStackTrace(); | |
76 | return false; | |
77 | } catch (IOException e) { | |
78 | e.printStackTrace(); | |
79 | return false; | |
80 | } | |
81 | ||
82 | } | |
83 | ||
84 | public boolean Logout() { | |
85 | String LOGOUT_URL = "_api/logout"; | |
86 | HttpGet httpGet = new HttpGet(this.baseUrl + LOGOUT_URL); | |
87 | Configuration configuration = Configuration.getSingleton(); | |
88 | ||
89 | if (!Objects.equals(configuration.getSession(), "")) { | |
90 | httpGet.setHeader("Cookie", configuration.getSession()); | |
91 | ||
92 | //Execute and get the response. | |
93 | HttpResponse response = null; | |
94 | ||
95 | try { | |
96 | HttpClient httpClient = HttpClients.createDefault(); | |
97 | response = httpClient.execute(httpGet); | |
98 | HttpEntity entity = response.getEntity(); | |
99 | if (response.getStatusLine().getStatusCode() == 200) { | |
100 | configuration.setSession(""); | |
101 | configuration.setUser(""); | |
102 | configuration.setPassword(""); | |
103 | return true; | |
104 | } | |
105 | return false; | |
106 | } catch (IOException e) { | |
107 | e.printStackTrace(); | |
108 | return false; | |
109 | } | |
110 | } | |
111 | return true; | |
112 | } | |
113 | ||
114 | public ArrayList<String> GetWorkspaces() { | |
115 | ArrayList<String> workspaces = new ArrayList<>(); | |
116 | String WORKSPACES_URL = "_api/v2/ws/"; | |
117 | HttpGet httpGet = new HttpGet(this.baseUrl + WORKSPACES_URL); | |
118 | Configuration configuration = Configuration.getSingleton(); | |
119 | ||
120 | if (configuration.getSession() != "") { | |
121 | httpGet.setHeader("Cookie", configuration.getSession()); | |
122 | ||
123 | //Execute and get the response. | |
124 | HttpResponse response = null; | |
125 | InputStream instream = null; | |
126 | try { | |
127 | HttpClient httpClient = HttpClients.createDefault(); | |
128 | response = httpClient.execute(httpGet); | |
129 | HttpEntity entity = response.getEntity(); | |
130 | ||
131 | if (entity != null && response.getStatusLine().getStatusCode() == 200) { | |
132 | instream = entity.getContent(); | |
133 | ||
134 | BufferedReader br = new BufferedReader(new InputStreamReader(instream)); | |
135 | String output; | |
136 | JSONArray jsonArray = new JSONArray(); | |
137 | while ((output = br.readLine()) != null) { | |
138 | System.out.println(output); | |
139 | jsonArray = JSONArray.fromObject(output); | |
140 | } | |
141 | ||
142 | for (int i = 0; i < jsonArray.size(); i++) { | |
143 | JSONObject jsonObject = jsonArray.getJSONObject(i); | |
144 | workspaces.add(jsonObject.get("name").toString()); | |
145 | } | |
146 | } | |
147 | } catch (IOException e) { | |
148 | e.printStackTrace(); | |
149 | } finally { | |
150 | try { | |
151 | instream.close(); | |
152 | } catch (IOException e) { | |
153 | e.printStackTrace(); | |
154 | } | |
155 | } | |
156 | } | |
157 | ||
158 | return workspaces; | |
159 | } | |
160 | ||
161 | ||
162 | private int AddCommand(String commandName, String workspace, String session) { | |
163 | String COMMAND_URL = "_api/v2/ws/" + workspace + "/commands/"; | |
164 | HttpClient httpClient = HttpClients.createDefault(); | |
165 | HttpPost httpPost = new HttpPost(this.baseUrl + COMMAND_URL); | |
166 | ||
167 | try { | |
168 | StringEntity stringEntity = new StringEntity(ConvertCommandToParams(commandName).toString()); | |
169 | httpPost.setHeader("Cookie", session); | |
170 | httpPost.setHeader("Content-Type", "application/json"); | |
171 | httpPost.setEntity(stringEntity); | |
172 | HttpResponse response = httpClient.execute(httpPost); | |
173 | HttpEntity entity = response.getEntity(); | |
174 | ||
175 | if (response.getStatusLine().getStatusCode() == 200 || response.getStatusLine().getStatusCode() == 201 || response.getStatusLine().getStatusCode() == 409) { | |
176 | BufferedReader br = new BufferedReader(new InputStreamReader(entity.getContent())); | |
177 | String output; | |
178 | JSONObject json; | |
179 | String commandStr = "-1"; | |
180 | while ((output = br.readLine()) != null) { | |
181 | json = JSONObject.fromObject(output); | |
182 | if (response.getStatusLine().getStatusCode() == 409) { | |
183 | JSONObject jsonObject = JSONObject.fromObject(json.get("object")); | |
184 | commandStr = jsonObject.get("_id").toString(); | |
185 | } else { | |
186 | commandStr = json.get("_id").toString(); | |
187 | } | |
188 | } | |
189 | return Integer.parseInt(commandStr); | |
190 | } | |
191 | return -1; | |
192 | } catch (UnsupportedEncodingException e) { | |
193 | // writing error to Log | |
194 | e.printStackTrace(); | |
195 | return -1; | |
196 | } catch (ClientProtocolException e) { | |
197 | e.printStackTrace(); | |
198 | return -1; | |
199 | } catch (IOException e) { | |
200 | e.printStackTrace(); | |
201 | return -1; | |
202 | } | |
203 | } | |
204 | ||
205 | private int AddHost(Alert alert, String workspace, String session) { | |
206 | String VULN_URL = "_api/v2/ws/" + workspace + "/hosts/"; | |
207 | HttpClient httpClient = HttpClients.createDefault(); | |
208 | HttpPost httpPost = new HttpPost(this.baseUrl + VULN_URL); | |
209 | ||
210 | try { | |
211 | StringEntity stringEntity = new StringEntity(ConvertHostToParams(alert).toString()); | |
212 | httpPost.setHeader("Cookie", session); | |
213 | httpPost.setHeader("Content-Type", "application/json"); | |
214 | httpPost.setEntity(stringEntity); | |
215 | HttpResponse response = httpClient.execute(httpPost); | |
216 | HttpEntity entity = response.getEntity(); | |
217 | ||
218 | if (response.getStatusLine().getStatusCode() == 200 || response.getStatusLine().getStatusCode() == 201 || response.getStatusLine().getStatusCode() == 409) { | |
219 | BufferedReader br = new BufferedReader(new InputStreamReader(entity.getContent())); | |
220 | String output; | |
221 | JSONObject json; | |
222 | String hostStr = "-1"; | |
223 | while ((output = br.readLine()) != null) { | |
224 | json = JSONObject.fromObject(output); | |
225 | if (response.getStatusLine().getStatusCode() == 409) { | |
226 | JSONObject jsonObject = JSONObject.fromObject(json.get("object")); | |
227 | hostStr = jsonObject.get("id").toString(); | |
228 | } else { | |
229 | hostStr = json.get("id").toString(); | |
230 | } | |
231 | } | |
232 | return Integer.parseInt(hostStr); | |
233 | } | |
234 | return -1; | |
235 | } catch (UnsupportedEncodingException e) { | |
236 | // writing error to Log | |
237 | e.printStackTrace(); | |
238 | return -1; | |
239 | } catch (ClientProtocolException e) { | |
240 | e.printStackTrace(); | |
241 | return -1; | |
242 | } catch (IOException e) { | |
243 | e.printStackTrace(); | |
244 | return -1; | |
245 | } | |
246 | } | |
247 | ||
248 | private int AddService(Alert alert, String workspace, String session, int hostId) { | |
249 | String VULN_URL = "_api/v2/ws/" + workspace + "/services/"; | |
250 | HttpClient httpClient = HttpClients.createDefault(); | |
251 | HttpPost httpPost = new HttpPost(this.baseUrl + VULN_URL); | |
252 | ||
253 | try { | |
254 | StringEntity stringEntity = new StringEntity(ConvertServiceToParams(alert, hostId).toString()); | |
255 | httpPost.setHeader("Cookie", session); | |
256 | httpPost.setHeader("Content-Type", "application/json"); | |
257 | httpPost.setEntity(stringEntity); | |
258 | HttpResponse response = httpClient.execute(httpPost); | |
259 | HttpEntity entity = response.getEntity(); | |
260 | ||
261 | BufferedReader br = new BufferedReader(new InputStreamReader(entity.getContent())); | |
262 | String output; | |
263 | if (response.getStatusLine().getStatusCode() == 200 || response.getStatusLine().getStatusCode() == 201 || response.getStatusLine().getStatusCode() == 409) { | |
264 | JSONObject json; | |
265 | String serviceStr = "-1"; | |
266 | while ((output = br.readLine()) != null) { | |
267 | json = JSONObject.fromObject(output); | |
268 | if (response.getStatusLine().getStatusCode() == 409) { | |
269 | JSONObject jsonObject = JSONObject.fromObject(json.get("object")); | |
270 | serviceStr = jsonObject.get("id").toString(); | |
271 | } else { | |
272 | serviceStr = json.get("id").toString(); | |
273 | } | |
274 | } | |
275 | return Integer.parseInt(serviceStr); | |
276 | } else { | |
277 | while ((output = br.readLine()) != null) { | |
278 | System.out.println(output); | |
279 | } | |
280 | ||
281 | return -1; | |
282 | } | |
283 | ||
284 | } catch (UnsupportedEncodingException e) { | |
285 | // writing error to Log | |
286 | e.printStackTrace(); | |
287 | return -1; | |
288 | } catch (ClientProtocolException e) { | |
289 | e.printStackTrace(); | |
290 | return -1; | |
291 | } catch (IOException e) { | |
292 | e.printStackTrace(); | |
293 | return -1; | |
294 | } | |
295 | } | |
296 | ||
297 | public int AddVulnerability(Alert alert, String workspace, String session) { | |
298 | int hostId = AddHost(alert, workspace, session); | |
299 | if (hostId == -1) { | |
300 | return 500; | |
301 | } | |
302 | ||
303 | String parentType = "Service"; | |
304 | int serviceId = AddService(alert, workspace, session, hostId); | |
305 | if (serviceId == -1) { | |
306 | return 500; | |
307 | } | |
308 | ||
309 | ||
310 | String commandName = messages.getString("faraday.tool.command.name"); | |
311 | int commandId = AddCommand(commandName, workspace, session); | |
312 | if (commandId == -1) { | |
313 | return 500; | |
314 | } | |
315 | ||
316 | ||
317 | String VULN_URL = "_api/v2/ws/" + workspace + "/vulns/?command_id=" + commandId; | |
318 | HttpClient httpClient = HttpClients.createDefault(); | |
319 | HttpPost httpPost = new HttpPost(this.baseUrl + VULN_URL); | |
320 | try { | |
321 | ||
322 | StringEntity stringEntity = new StringEntity(ConvertAlertToParams(alert, workspace, parentType, serviceId).toString()); | |
323 | httpPost.setHeader("Cookie", session); | |
324 | httpPost.setHeader("Content-Type", "application/json"); | |
325 | httpPost.setEntity(stringEntity); | |
326 | HttpResponse response = httpClient.execute(httpPost); | |
327 | return response.getStatusLine().getStatusCode(); | |
328 | ||
329 | } catch (UnsupportedEncodingException e) { | |
330 | // writing error to Log | |
331 | e.printStackTrace(); | |
332 | return 402; | |
333 | } catch (ClientProtocolException e) { | |
334 | e.printStackTrace(); | |
335 | return 402; | |
336 | } catch (IOException e) { | |
337 | e.printStackTrace(); | |
338 | return 402; | |
339 | } | |
340 | } | |
341 | ||
342 | private JSONObject ConvertAlertToParams(Alert alert, String workspace, String parentType, int parentId) { | |
343 | // Request parameters and other properties. | |
344 | JSONObject params = new JSONObject(); | |
345 | ||
346 | params.put("name", alert.getName()); | |
347 | params.put("ws", workspace); | |
348 | params.put("request", alert.getMessage().getRequestHeader().toString()); | |
349 | params.put("response", alert.getMessage().getResponseHeader().toString()); | |
350 | String desc = !alert.getParam().equals("") ? alert.getDescription() + "\nWith parameter: '" + alert.getParam() + "'" : | |
351 | alert.getDescription(); | |
352 | params.put("desc", desc); | |
353 | params.put("resolution", alert.getSolution()); | |
354 | params.put("type", "VulnerabilityWeb"); | |
355 | params.put("data", alert.getPostData()); | |
356 | params.put("policyviolations", "[]"); | |
357 | params.put("parent_type", parentType); | |
358 | params.put("parent", parentId); | |
359 | params.put("params", alert.getParam()); | |
360 | ||
361 | JSONObject metadata = new JSONObject(); | |
362 | metadata.put("creator", "OWASP"); | |
363 | params.put("metadata", metadata); | |
364 | ||
365 | String hostname = alert.getMessage().getRequestHeader().getHostName(); | |
366 | String IpAddres = GetIPFromHostname(hostname); | |
367 | JSONArray hostNamesArray = new JSONArray(); | |
368 | hostNamesArray.add(hostname); | |
369 | hostNamesArray.add(IpAddres); | |
370 | params.put("hostnames", hostNamesArray); | |
371 | params.put("target", IpAddres); | |
372 | params.put("website", hostname); | |
373 | ||
374 | JSONArray refsJsonArray = new JSONArray(); | |
375 | String[] resfArray = alert.getReference().split("\n"); | |
376 | Collections.addAll(refsJsonArray, resfArray); | |
377 | params.put("refs", refsJsonArray); | |
378 | ||
379 | try { | |
380 | params.put("path", alert.getMsgUri().getPath()); | |
381 | } catch (URIException e) { | |
382 | e.printStackTrace(); | |
383 | } | |
384 | ||
385 | if (alert.getConfidence() == 4) { | |
386 | params.put("confirmed", true); | |
387 | } | |
388 | ||
389 | switch (alert.getRisk()) { | |
390 | case 0: | |
391 | params.put("severity", "informational"); | |
392 | break; | |
393 | case 1: | |
394 | params.put("severity", "low"); | |
395 | break; | |
396 | case 2: | |
397 | params.put("severity", "medium"); | |
398 | break; | |
399 | case 3: | |
400 | params.put("severity", "high"); | |
401 | break; | |
402 | } | |
403 | return params; | |
404 | ||
405 | } | |
406 | ||
407 | private JSONObject ConvertHostToParams(Alert alert) { | |
408 | // Request parameters and other properties. | |
409 | JSONObject params = new JSONObject(); | |
410 | ||
411 | try { | |
412 | String ipAddress = GetIPFromHostname(alert.getMsgUri().getHost()); | |
413 | params.put("ip", ipAddress); | |
414 | params.put("name", alert.getMsgUri().getName()); | |
415 | params.put("os", "Unknown"); | |
416 | params.put("description", ""); | |
417 | JSONObject metadata = new JSONObject(); | |
418 | metadata.put("creator", "Zap"); | |
419 | params.put("metadata", metadata); | |
420 | ||
421 | String hostname = alert.getMessage().getRequestHeader().getHostName(); | |
422 | JSONArray hostNamesArray = new JSONArray(); | |
423 | hostNamesArray.add(hostname); | |
424 | params.put("hostnames", hostNamesArray); | |
425 | ||
426 | } catch (URIException e) { | |
427 | e.printStackTrace(); | |
428 | } | |
429 | return params; | |
430 | } | |
431 | ||
432 | private JSONObject ConvertServiceToParams(Alert alert, int parentId) { | |
433 | // Request parameters and other properties. | |
434 | JSONObject params = new JSONObject(); | |
435 | JSONArray portsJson = new JSONArray(); | |
436 | portsJson.add(alert.getMessage().getRequestHeader().getHostPort()); | |
437 | params.put("ports", portsJson); | |
438 | params.put("parent", parentId); | |
439 | params.put("status", "open"); | |
440 | params.put("type", "Service"); | |
441 | params.put("description", ""); | |
442 | JSONObject metadata = new JSONObject(); | |
443 | metadata.put("creator", "OWASP"); | |
444 | params.put("metadata", metadata); | |
445 | ||
446 | switch (alert.getMessage().getRequestHeader().getHostPort()) { | |
447 | case 21: | |
448 | params.put("name", "FTP"); | |
449 | params.put("protocol", "tcp"); | |
450 | break; | |
451 | case 22: | |
452 | params.put("name", "SSH"); | |
453 | params.put("protocol", "tcp"); | |
454 | break; | |
455 | case 23: | |
456 | params.put("name", "TELNET"); | |
457 | params.put("protocol", "tcp"); | |
458 | break; | |
459 | case 25: | |
460 | params.put("name", "SMTP"); | |
461 | params.put("protocol", "tcp"); | |
462 | break; | |
463 | case 80: | |
464 | params.put("name", "HTTP"); | |
465 | params.put("protocol", "tcp"); | |
466 | break; | |
467 | case 110: | |
468 | params.put("name", "POP"); | |
469 | params.put("protocol", "tcp"); | |
470 | break; | |
471 | case 443: | |
472 | params.put("name", "SSL"); | |
473 | params.put("protocol", "tcp"); | |
474 | break; | |
475 | default: | |
476 | params.put("name", "unknown"); | |
477 | params.put("protocol", "unknown"); | |
478 | break; | |
479 | } | |
480 | ||
481 | return params; | |
482 | } | |
483 | ||
484 | private JSONObject ConvertCommandToParams(String commandName) { | |
485 | // Request parameters and other properties. | |
486 | JSONObject params = new JSONObject(); | |
487 | params.put("itime", Instant.EPOCH.getEpochSecond()); | |
488 | params.put("import_source", "shell"); | |
489 | params.put("duration", ""); | |
490 | params.put("command", "Zap"); | |
491 | params.put("tool", commandName); | |
492 | return params; | |
493 | } | |
494 | ||
495 | private String GetIPFromHostname(String hostname) { | |
496 | try { | |
497 | InetAddress inetAddr = InetAddress.getByName(hostname); | |
498 | byte[] addr = inetAddr.getAddress(); | |
499 | ||
500 | // Convert to dot representation | |
501 | String ipAddr = ""; | |
502 | for (int i = 0; i < addr.length; i++) { | |
503 | if (i > 0) { | |
504 | ipAddr += "."; | |
505 | } | |
506 | ||
507 | ipAddr += addr[i] & 0xFF; | |
508 | } | |
509 | ||
510 | System.out.println("IP Address: " + ipAddr); | |
511 | return ipAddr; | |
512 | } catch (UnknownHostException e) { | |
513 | System.out.println("Host not found: " + e.getMessage()); | |
514 | return ""; | |
515 | } | |
516 | } | |
517 | ||
518 | public void setBaseUrl(String baseUrl) { | |
519 | this.baseUrl = baseUrl; | |
520 | } | |
521 | } | |
522 | ||
523 | ||
524 |
0 | /* | |
1 | * Zed Attack Proxy (ZAP) and its related class files. | |
2 | * | |
3 | * ZAP is an HTTP/HTTPS proxy for assessing web application security. | |
4 | * | |
5 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | * you may not use this file except in compliance with the License. | |
7 | * You may obtain a copy of the License at | |
8 | * | |
9 | * http://www.apache.org/licenses/LICENSE-2.0 | |
10 | * | |
11 | * Unless required by applicable law or agreed to in writing, software | |
12 | * distributed under the License is distributed on an "AS IS" BASIS, | |
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | * See the License for the specific language governing permissions and | |
15 | * limitations under the License. | |
16 | */ | |
17 | package org.zaproxy.zap.extension.faraday; | |
18 | ||
19 | import org.apache.log4j.Logger; | |
20 | import org.parosproxy.paros.Constant; | |
21 | import org.parosproxy.paros.control.Control; | |
22 | import org.parosproxy.paros.extension.ExtensionAdaptor; | |
23 | import org.parosproxy.paros.extension.ExtensionHook; | |
24 | import org.parosproxy.paros.extension.ExtensionPopupMenuItem; | |
25 | import org.zaproxy.zap.view.ZapMenuItem; | |
26 | ||
27 | import javax.swing.*; | |
28 | import java.awt.*; | |
29 | import java.awt.event.KeyEvent; | |
30 | import java.io.*; | |
31 | import java.util.Properties; | |
32 | import java.util.ResourceBundle; | |
33 | ||
34 | public class FaradayExtension extends ExtensionAdaptor { | |
35 | private static final Logger logger = Logger.getLogger(FaradayExtension.class); | |
36 | private ZapMenuItem menuItemFaradayConfig; | |
37 | private ConfigurationDialog configurationDialog; | |
38 | private PopupMenuItemSendAlert popupMenuItemSendAlert; | |
39 | private PopupMenuItemSendRequest popupMenuItemSendRequest; | |
40 | private ResourceBundle messages = null; | |
41 | ||
42 | ||
43 | ||
44 | public FaradayExtension(String name) { | |
45 | super(name); | |
46 | } | |
47 | ||
48 | ||
49 | public FaradayExtension() { | |
50 | super(); | |
51 | initialize(); | |
52 | } | |
53 | ||
54 | ||
55 | private void initialize() { | |
56 | messages = ResourceBundle.getBundle( | |
57 | this.getClass().getPackage().getName() + | |
58 | ".Messages", Constant.getLocale()); | |
59 | this.setName(messages.getString("faraday.extension.name")); | |
60 | this.initConfiguration(); | |
61 | } | |
62 | ||
63 | @Override | |
64 | public String getAuthor() { | |
65 | return messages.getString("faraday.extension.author"); | |
66 | } | |
67 | ||
68 | @Override | |
69 | public void hook(ExtensionHook extensionHook) { | |
70 | super.hook(extensionHook); | |
71 | ||
72 | if (getView() != null) { | |
73 | extensionHook.getHookMenu().addToolsMenuItem(getMenuItemFaradayConfig()); | |
74 | extensionHook.getHookMenu().addPopupMenuItem(this.getPopupMenuItem()); | |
75 | extensionHook.getHookMenu().addPopupMenuItem(this.getPopupMenuItemRequest()); | |
76 | } | |
77 | } | |
78 | ||
79 | @Override | |
80 | public boolean canUnload() { | |
81 | return true; | |
82 | } | |
83 | ||
84 | private ZapMenuItem getMenuItemFaradayConfig() { | |
85 | if (menuItemFaradayConfig == null) { | |
86 | menuItemFaradayConfig = new ZapMenuItem( | |
87 | "faraday.menu.tools.label", | |
88 | KeyStroke.getKeyStroke( | |
89 | KeyEvent.VK_F, | |
90 | Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() | KeyEvent.ALT_DOWN_MASK, | |
91 | false)); | |
92 | menuItemFaradayConfig.setEnabled(Control.getSingleton().getMode() != Control.Mode.safe); | |
93 | ||
94 | menuItemFaradayConfig.addActionListener(new java.awt.event.ActionListener() { | |
95 | ||
96 | @Override | |
97 | public void actionPerformed(java.awt.event.ActionEvent e) { | |
98 | showConfigurationDialog(); | |
99 | } | |
100 | }); | |
101 | } | |
102 | return menuItemFaradayConfig; | |
103 | } | |
104 | ||
105 | ||
106 | private void showConfigurationDialog() { | |
107 | if (configurationDialog == null) { | |
108 | configurationDialog = new ConfigurationDialog(messages.getString("faraday.config.dialog.title")); | |
109 | configurationDialog.init(); | |
110 | } | |
111 | configurationDialog.setVisible(true); | |
112 | } | |
113 | ||
114 | ||
115 | private ExtensionPopupMenuItem getPopupMenuItem() { | |
116 | if (popupMenuItemSendAlert == null) { | |
117 | popupMenuItemSendAlert = new PopupMenuItemSendAlert(messages.getString("faraday.button.send.alert")); | |
118 | } | |
119 | ||
120 | return popupMenuItemSendAlert; | |
121 | ||
122 | } | |
123 | ||
124 | ||
125 | private ExtensionPopupMenuItem getPopupMenuItemRequest() { | |
126 | if (popupMenuItemSendRequest == null) { | |
127 | popupMenuItemSendRequest = new PopupMenuItemSendRequest(messages.getString("faraday.button.send.request")); | |
128 | } | |
129 | ||
130 | return popupMenuItemSendRequest; | |
131 | ||
132 | } | |
133 | ||
134 | ||
135 | private void initConfiguration() { | |
136 | Configuration configuration = Configuration.getSingleton(); | |
137 | ||
138 | Properties prop = new Properties(); | |
139 | InputStream input = null; | |
140 | ||
141 | try { | |
142 | String filePath = Constant.getZapHome() + "faraday" + File.separator + "default.properties"; | |
143 | input = new FileInputStream(filePath); | |
144 | ||
145 | // load a properties file | |
146 | prop.load(input); | |
147 | ||
148 | // set the properties value | |
149 | String fUser = prop.getProperty("default"); | |
150 | configuration.restore(fUser); | |
151 | ||
152 | } catch (IOException io) { | |
153 | System.out.println("We can't found default.properties file"); | |
154 | } finally { | |
155 | if (input != null) { | |
156 | try { | |
157 | input.close(); | |
158 | } catch (IOException e) { | |
159 | e.printStackTrace(); | |
160 | } | |
161 | } | |
162 | ||
163 | } | |
164 | } | |
165 | ||
166 | } |
0 | # An example ZAP extension which adds a top level menu item. | |
1 | # | |
2 | # This file defines the default (English) variants of all of the | |
3 | ||
4 | faraday.extension.name = Faraday Extension | |
5 | faraday.extension.author = Jorge Luis Gonzàlez Iznaga | |
6 | ||
7 | faraday.menu.tools.label = Faraday configuration options | |
8 | faraday.config.dialog.title = Faraday configuration | |
9 | faraday.config.dialog.tabs.auth = Authorization | |
10 | faraday.config.dialog.tabs.conf = Configuration | |
11 | faraday.config.dialog.auth.user = Faraday user | |
12 | faraday.config.dialog.auth.pass = Faraday password | |
13 | faraday.config.dialog.server = Faraday server | |
14 | faraday.config.dialog.auth.login = Login | |
15 | faraday.config.dialog.auth.logout = Logout | |
16 | faraday.config.dialog.import.current = Import current vulnerabilities | |
17 | faraday.config.dialog.import.new = Import new vulnerabilities | |
18 | faraday.config.dialog.restore = Restore configuration | |
19 | faraday.config.dialog.refresh = Refresh | |
20 | faraday.config.dialog.save = Save configuration | |
21 | faraday.config.dialog.workspace = Select faraday workspace | |
22 | faraday.config.dialog.default = Set this configuration as default | |
23 | ||
24 | faraday.config.dialog.tab.auth = Authorization | |
25 | faraday.config.dialog.tab.conf = Configuration | |
26 | ||
27 | faraday.dialog.enter.user = Please enter your user | |
28 | ||
29 | ||
30 | faraday.button.send.alert = Send alert to Faraday | |
31 | faraday.button.send.request = Send request to Faraday | |
32 | ||
33 | faraday.message.invalid.check.credentials = Please, check your credentials | |
34 | faraday.message.invalid.credentials = Invalid credentials | |
35 | faraday.dialog.login.title = Faraday login | |
36 | faraday.dialog.logout.title = Faraday logout | |
37 | faraday.dialog.logout.success = You're logout successfully ! | |
38 | faraday.dialog.logout.error = We can't complete logout operation | |
39 | ||
40 | faraday.dialog.button.close = Close | |
41 | ||
42 | faraday.save.config.success = Configuration saved successfully | |
43 | faraday.save.config.error = We can't save your configuration, please check home ZAP directory | |
44 | faraday.restore.config.error = You haven't a configuration saved with this user | |
45 | faraday.refresh.workspace.done = Your workspaces are up to date | |
46 | faraday.restore.config.error.login = Unable to restore this configuration | |
47 | faraday.set.default.config.error = We can't set your configuration | |
48 | ||
49 | faraday.send.alert.permissions.error = You should check your current workspace and your permissions | |
50 | faraday.send.alert.conflict = This alert already exists in Faraday | |
51 | faraday.send.request.conflict = This request already exists in Faraday | |
52 | faraday.send.alert.success = Alert added successfully | |
53 | faraday.send.request.success = Request added successfully | |
54 | ||
55 | faraday.tool.command.name = OWASP⏎ |
0 | package org.zaproxy.zap.extension.faraday; | |
1 | ||
2 | import org.apache.log4j.Logger; | |
3 | import org.parosproxy.paros.Constant; | |
4 | import org.parosproxy.paros.core.scanner.Alert; | |
5 | import org.parosproxy.paros.view.View; | |
6 | import org.zaproxy.zap.extension.alert.AlertNode; | |
7 | import org.zaproxy.zap.extension.alert.PopupMenuItemAlert; | |
8 | ||
9 | import javax.swing.*; | |
10 | import java.awt.*; | |
11 | import java.util.ResourceBundle; | |
12 | ||
13 | public class PopupMenuItemSendAlert extends PopupMenuItemAlert { | |
14 | private static final Logger logger = Logger.getLogger(PopupMenuItemSendAlert.class); | |
15 | private FaradayClient faradayClient; | |
16 | private ResourceBundle messages = null; | |
17 | private int selectionCount = 0; | |
18 | private int totalSelectionCount = 0; | |
19 | private boolean treeAlertParentSelected = false; | |
20 | ||
21 | public PopupMenuItemSendAlert(String label) { | |
22 | super(label, true); | |
23 | Configuration configuration = Configuration.getSingleton(); | |
24 | faradayClient = new FaradayClient(configuration.getServer()); | |
25 | messages = ResourceBundle.getBundle( | |
26 | this.getClass().getPackage().getName() + | |
27 | ".Messages", Constant.getLocale()); | |
28 | } | |
29 | ||
30 | @Override | |
31 | protected void performAction(Alert alert) { | |
32 | Configuration configuration = Configuration.getSingleton(); | |
33 | String workspace = configuration.getWorkspace(); | |
34 | String session = configuration.getSession(); | |
35 | if (workspace != null && session != null && !workspace.equals("") && !session.equals("")) { | |
36 | int responseCode = faradayClient.AddVulnerability(alert, configuration.getWorkspace(), session); | |
37 | String message; | |
38 | int iconMessage = 1; | |
39 | switch (responseCode) { | |
40 | case 200: | |
41 | case 201: | |
42 | case 409: | |
43 | message = messages.getString("faraday.send.alert.success"); | |
44 | break; | |
45 | case 403: | |
46 | message = messages.getString("faraday.send.alert.permissions.error"); | |
47 | iconMessage = JOptionPane.WARNING_MESSAGE; | |
48 | break; | |
49 | // case 409: | |
50 | // message = messages.getString("faraday.send.alert.conflict"); | |
51 | // iconMessage = JOptionPane.WARNING_MESSAGE; | |
52 | // break; | |
53 | case 400: | |
54 | case 500: | |
55 | message = "Unable to send " + alert.getName() + " to Faraday"; | |
56 | iconMessage = JOptionPane.ERROR_MESSAGE; | |
57 | break; | |
58 | ||
59 | default: | |
60 | message = "Unable to send " + alert.getName() + " to Faraday"; | |
61 | iconMessage = JOptionPane.ERROR_MESSAGE; | |
62 | break; | |
63 | } | |
64 | ||
65 | if (canShowMessageDialog()/*this.selectionCount == 1 && !treeAlertParentSelected*/) { | |
66 | JOptionPane.showMessageDialog( | |
67 | this, | |
68 | message, | |
69 | messages.getString("faraday.button.send.alert"), | |
70 | iconMessage); | |
71 | } | |
72 | ||
73 | ||
74 | logger.error(message); | |
75 | if (View.isInitialised()) { | |
76 | // Report info to the Output tab | |
77 | View.getSingleton().getOutputPanel().append(message + "\n"); | |
78 | } | |
79 | ||
80 | ||
81 | } else { | |
82 | if (canShowMessageDialog()) { | |
83 | JOptionPane.showMessageDialog( | |
84 | this, | |
85 | messages.getString("faraday.send.alert.permissions.error"), | |
86 | messages.getString("faraday.button.send.alert"), | |
87 | JOptionPane.ERROR_MESSAGE); | |
88 | logger.error(messages.getString("faraday.send.alert.permissions.error")); | |
89 | } | |
90 | ||
91 | ||
92 | if (View.isInitialised()) { | |
93 | // Report info to the Output tab | |
94 | View.getSingleton().getOutputPanel().append(messages.getString("faraday.send.alert.permissions.error") + "\n"); | |
95 | } | |
96 | } | |
97 | ||
98 | } | |
99 | ||
100 | @Override | |
101 | public boolean isEnableForComponent(Component invoker) { | |
102 | logger.info(invoker.getName()); | |
103 | this.totalSelectionCount = 0; | |
104 | try { | |
105 | if (Configuration.getSingleton().getSession() == null || Configuration.getSingleton().getSession().equals("")) { | |
106 | return false; | |
107 | } | |
108 | treeAlertParentSelected = ((JTree) invoker).isRowSelected(0); | |
109 | if (super.isEnableForComponent(invoker) || treeAlertParentSelected) { | |
110 | this.selectionCount = ((JTree) invoker).getSelectionCount(); | |
111 | for (int i = 0; i < ((JTree) invoker).getSelectionPaths().length; i++) { | |
112 | AlertNode nodeTemp = (AlertNode) ((JTree) invoker).getSelectionPaths()[i].getLastPathComponent(); | |
113 | this.totalSelectionCount += getTotalAlertsToProcess(nodeTemp); | |
114 | } | |
115 | ||
116 | setEnabled(true); | |
117 | return true; | |
118 | } | |
119 | return false; | |
120 | } catch (Exception e) { | |
121 | return false; | |
122 | } | |
123 | } | |
124 | ||
125 | ||
126 | private int getTotalAlertsToProcess(AlertNode node) { | |
127 | if (node.getChildCount() > 0) { | |
128 | int total = 0; | |
129 | for (int i = 0; i < node.getChildCount(); i++) { | |
130 | total += getTotalAlertsToProcess(node.getChildAt(i)); | |
131 | } | |
132 | return total; | |
133 | } else { | |
134 | return 1; | |
135 | } | |
136 | ||
137 | } | |
138 | ||
139 | private boolean canShowMessageDialog() { | |
140 | this.totalSelectionCount--; | |
141 | if (this.treeAlertParentSelected) { | |
142 | this.totalSelectionCount = 1; | |
143 | this.treeAlertParentSelected = false; | |
144 | } | |
145 | ||
146 | return this.totalSelectionCount == 0; | |
147 | } | |
148 | } |
0 | package org.zaproxy.zap.extension.faraday; | |
1 | ||
2 | import org.apache.log4j.Logger; | |
3 | import org.parosproxy.paros.Constant; | |
4 | import org.parosproxy.paros.core.scanner.Alert; | |
5 | import org.parosproxy.paros.db.DatabaseException; | |
6 | import org.parosproxy.paros.db.RecordAlert; | |
7 | import org.parosproxy.paros.model.HistoryReference; | |
8 | import org.parosproxy.paros.network.HttpMalformedHeaderException; | |
9 | import org.parosproxy.paros.view.View; | |
10 | import org.zaproxy.zap.extension.alert.PopupMenuAlert; | |
11 | import org.zaproxy.zap.view.messagecontainer.http.HttpMessageContainer; | |
12 | import org.zaproxy.zap.view.popup.PopupMenuItemHistoryReferenceContainer; | |
13 | ||
14 | import javax.swing.*; | |
15 | import java.awt.*; | |
16 | import java.util.Iterator; | |
17 | import java.util.List; | |
18 | import java.util.ResourceBundle; | |
19 | ||
20 | ||
21 | public class PopupMenuItemSendRequest extends PopupMenuItemHistoryReferenceContainer { | |
22 | private FaradayClient faradayClient; | |
23 | private ResourceBundle messages = null; | |
24 | private int selectionCount = 0; | |
25 | private static final Logger logger = Logger.getLogger(PopupMenuItemSendRequest.class); | |
26 | ||
27 | ||
28 | public PopupMenuItemSendRequest(String label) { | |
29 | super(label, true); | |
30 | Configuration configuration = Configuration.getSingleton(); | |
31 | faradayClient = new FaradayClient(configuration.getServer()); | |
32 | messages = ResourceBundle.getBundle( | |
33 | this.getClass().getPackage().getName() + | |
34 | ".Messages", Constant.getLocale()); | |
35 | } | |
36 | ||
37 | @Override | |
38 | public void performAction(HistoryReference href) { | |
39 | try { | |
40 | Alert alert = new Alert(new RecordAlert(), href); | |
41 | alert.setName(href.getSiteNode().getName()); | |
42 | alert.setUri(href.getURI().toString()); | |
43 | alert.setMessage(href.getHttpMessage()); | |
44 | alert.setDescription(""); | |
45 | alert.setRiskConfidence(0, 0); | |
46 | ||
47 | Configuration configuration = Configuration.getSingleton(); | |
48 | String workspace = configuration.getWorkspace(); | |
49 | String session = configuration.getSession(); | |
50 | if (workspace != null && session != null && !workspace.equals("") && !session.equals("")) { | |
51 | int responseCode = faradayClient.AddVulnerability(alert, configuration.getWorkspace(), session); | |
52 | String message = ""; | |
53 | int iconMessage = 1; | |
54 | switch (responseCode) { | |
55 | case 403: | |
56 | message = messages.getString("faraday.send.alert.permissions.error"); | |
57 | iconMessage = JOptionPane.WARNING_MESSAGE; | |
58 | break; | |
59 | case 409: | |
60 | message = messages.getString("faraday.send.request.conflict"); | |
61 | iconMessage = JOptionPane.WARNING_MESSAGE; | |
62 | break; | |
63 | case 500: | |
64 | message = "Unable to send " + alert.getName() + " to Faraday"; | |
65 | iconMessage = JOptionPane.ERROR_MESSAGE; | |
66 | break; | |
67 | case 201: | |
68 | message = messages.getString("faraday.send.request.success"); | |
69 | break; | |
70 | } | |
71 | ||
72 | if (this.selectionCount == 1) { | |
73 | JOptionPane.showMessageDialog( | |
74 | this, | |
75 | message, | |
76 | messages.getString("faraday.button.send.alert"), | |
77 | iconMessage); | |
78 | } | |
79 | ||
80 | logger.error(message); | |
81 | if (View.isInitialised()) { | |
82 | // Report info to the Output tab | |
83 | View.getSingleton().getOutputPanel().append(message + "\n"); | |
84 | } | |
85 | ||
86 | ||
87 | } else { | |
88 | JOptionPane.showMessageDialog( | |
89 | this, | |
90 | messages.getString("faraday.send.alert.permissions.error"), | |
91 | messages.getString("faraday.button.send.request"), | |
92 | JOptionPane.ERROR_MESSAGE); | |
93 | ||
94 | logger.error(messages.getString("faraday.send.alert.permissions.error")); | |
95 | if (View.isInitialised()) { | |
96 | // Report info to the Output tab | |
97 | View.getSingleton().getOutputPanel().append(messages.getString("faraday.send.alert.permissions.error") + "\n"); | |
98 | } | |
99 | } | |
100 | ||
101 | ||
102 | } catch (HttpMalformedHeaderException e) { | |
103 | e.printStackTrace(); | |
104 | } catch (DatabaseException e) { | |
105 | e.printStackTrace(); | |
106 | } | |
107 | } | |
108 | ||
109 | ||
110 | @Override | |
111 | public void performHistoryReferenceActions(List<HistoryReference> hrefs) { | |
112 | this.selectionCount = hrefs.size(); | |
113 | ||
114 | for (HistoryReference href : hrefs) { | |
115 | this.performAction(href); | |
116 | } | |
117 | } | |
118 | ||
119 | @Override | |
120 | public boolean isEnableForInvoker(Invoker invoker, HttpMessageContainer httpMessageContainer) { | |
121 | if (Configuration.getSingleton().getSession() == null || Configuration.getSingleton().getSession().equals("") || | |
122 | invoker.name().equals("ALERTS_PANEL")) { | |
123 | return false; | |
124 | } | |
125 | return super.isEnableForInvoker(invoker, httpMessageContainer); | |
126 | } | |
127 | ||
128 | @Override | |
129 | public boolean isButtonEnabledForHistoryReference(HistoryReference href) { | |
130 | if (Configuration.getSingleton().getSession() == null || Configuration.getSingleton().getSession().equals("")) { | |
131 | return false; | |
132 | } | |
133 | ||
134 | return href.getSiteNode() != null && super.isButtonEnabledForHistoryReference(href); | |
135 | } | |
136 | }⏎ |
0 | <zapaddon> | |
1 | <name>Faraday</name> | |
2 | <version>1</version> | |
3 | <status>release</status> | |
4 | <description>This extension integrates ZAP with the Faraday Integrated Penetration-Test Environment</description> | |
5 | <author>Jorge Luis González Iznaga</author> | |
6 | <extensions> | |
7 | <extension>org.zaproxy.zap.extension.faraday.FaradayExtension</extension> | |
8 | </extensions> | |
9 | <ascanrules/> | |
10 | <pscanrules/> | |
11 | <not-before-version>2.7.0</not-before-version> | |
12 | <not-from-version/> | |
13 | </zapaddon> |
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 os | |
7 | import socket | |
8 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
9 | ||
10 | ||
11 | try: | |
12 | import xml.etree.cElementTree as ET | |
13 | ETREE_VERSION = ET.VERSION | |
14 | except ImportError: | |
15 | import xml.etree.ElementTree as ET | |
16 | ETREE_VERSION = ET.VERSION | |
17 | ||
18 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] | |
19 | ||
20 | current_path = os.path.abspath(os.getcwd()) | |
21 | ||
22 | __author__ = "Francisco Amato" | |
23 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
24 | __credits__ = ["Francisco Amato"] | |
25 | __license__ = "" | |
26 | __version__ = "1.0.0" | |
27 | __maintainer__ = "Francisco Amato" | |
28 | __email__ = "[email protected]" | |
29 | __status__ = "Development" | |
30 | ||
31 | ||
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 | ||
41 | class ZapXmlParser: | |
42 | """ | |
43 | The objective of this class is to parse an xml | |
44 | file generated by the zap tool. | |
45 | ||
46 | TODO: Handle errors. | |
47 | TODO: Test zap output version. Handle what happens | |
48 | if the parser doesn't support it. | |
49 | ||
50 | TODO: Test cases. | |
51 | ||
52 | @param zap_xml_filepath A proper xml generated by zap | |
53 | """ | |
54 | ||
55 | def __init__(self, xml_output): | |
56 | ||
57 | tree = self.parse_xml(xml_output) | |
58 | ||
59 | if tree is not None: | |
60 | self.sites = [data for data in self.get_items(tree)] | |
61 | else: | |
62 | self.sites = [] | |
63 | ||
64 | def parse_xml(self, xml_output): | |
65 | """ | |
66 | Open and parse an xml file. | |
67 | ||
68 | TODO: Write custom parser to just read the nodes that we need instead of | |
69 | reading the whole file. | |
70 | ||
71 | @return xml_tree An xml tree instance. None if error. | |
72 | """ | |
73 | try: | |
74 | parser = ET.XMLParser(target=ET.TreeBuilder()) | |
75 | parser.feed(xml_output) | |
76 | tree = parser.close() | |
77 | ||
78 | except SyntaxError as err: | |
79 | print("SyntaxError: %s. %s" % (err, xml_output)) | |
80 | return None | |
81 | ||
82 | return tree | |
83 | ||
84 | def get_items(self, tree): | |
85 | """ | |
86 | @return items A list of Host instances | |
87 | """ | |
88 | for node in tree.findall('site'): | |
89 | yield Site(node) | |
90 | ||
91 | ||
92 | def get_attrib_from_subnode(xml_node, subnode_xpath_expr, attrib_name): | |
93 | """ | |
94 | Finds a subnode in the item node and the retrieves a value from it | |
95 | ||
96 | @return An attribute value | |
97 | """ | |
98 | global ETREE_VERSION | |
99 | node = None | |
100 | ||
101 | if ETREE_VERSION[0] <= 1 and ETREE_VERSION[1] < 3: | |
102 | ||
103 | match_obj = re.search( | |
104 | "([^\@]+?)\[\@([^=]*?)=\'([^\']*?)\'", | |
105 | subnode_xpath_expr) | |
106 | ||
107 | if match_obj is not None: | |
108 | ||
109 | node_to_find = match_obj.group(1) | |
110 | xpath_attrib = match_obj.group(2) | |
111 | xpath_value = match_obj.group(3) | |
112 | ||
113 | for node_found in xml_node.findall(node_to_find): | |
114 | ||
115 | if node_found.attrib[xpath_attrib] == xpath_value: | |
116 | node = node_found | |
117 | break | |
118 | else: | |
119 | node = xml_node.find(subnode_xpath_expr) | |
120 | ||
121 | else: | |
122 | node = xml_node.find(subnode_xpath_expr) | |
123 | ||
124 | if node is not None: | |
125 | return node.get(attrib_name) | |
126 | ||
127 | return None | |
128 | ||
129 | ||
130 | class Site: | |
131 | ||
132 | def __init__(self, item_node): | |
133 | ||
134 | self.node = item_node | |
135 | ||
136 | self.host = self.node.get('host') | |
137 | self.ip = self.resolve(self.host) | |
138 | self.port = self.node.get('port') | |
139 | ||
140 | self.items = [] | |
141 | for alert in self.node.findall('alerts/alertitem'): | |
142 | self.items.append(Item(alert)) | |
143 | ||
144 | def get_text_from_subnode(self, subnode_xpath_expr): | |
145 | """ | |
146 | Finds a subnode in the host node and the retrieves a value from it. | |
147 | ||
148 | @return An attribute value | |
149 | """ | |
150 | sub_node = self.node.find(subnode_xpath_expr) | |
151 | if sub_node is not None: | |
152 | return sub_node.text | |
153 | return None | |
154 | ||
155 | def resolve(self, host): | |
156 | ||
157 | try: | |
158 | return socket.gethostbyname(host) | |
159 | except: | |
160 | pass | |
161 | ||
162 | return host | |
163 | ||
164 | ||
165 | class Item: | |
166 | """ | |
167 | An abstract representation of a Item | |
168 | ||
169 | ||
170 | @param item_node A item_node taken from an zap xml tree | |
171 | """ | |
172 | ||
173 | def __init__(self, item_node): | |
174 | ||
175 | self.node = item_node | |
176 | self.id = self.get_text_from_subnode('pluginid') | |
177 | self.name = self.get_text_from_subnode('alert') | |
178 | self.severity = self.get_text_from_subnode('riskcode') | |
179 | self.desc = self.get_text_from_subnode('desc') | |
180 | ||
181 | if self.get_text_from_subnode('solution'): | |
182 | self.resolution = self.get_text_from_subnode('solution') | |
183 | else: | |
184 | self.resolution = '' | |
185 | ||
186 | if self.get_text_from_subnode('reference'): | |
187 | self.desc += '\nReference: ' + \ | |
188 | self.get_text_from_subnode('reference') | |
189 | ||
190 | self.ref = [] | |
191 | if self.get_text_from_subnode('cweid'): | |
192 | self.ref.append("CWE-" + self.get_text_from_subnode('cweid')) | |
193 | ||
194 | self.items = [] | |
195 | ||
196 | if item_node.find('instances'): | |
197 | arr = item_node.find('instances') | |
198 | else: | |
199 | arr = [item_node] | |
200 | ||
201 | for elem in arr: | |
202 | uri = elem.find('uri').text | |
203 | self.parse_uri(uri) | |
204 | ||
205 | self.requests = "\n".join([i['uri'] for i in self.items]) | |
206 | ||
207 | def parse_uri(self, uri): | |
208 | mregex = re.search( | |
209 | "(http|https|ftp)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&" | |
210 | ";%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]" | |
211 | "{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}" | |
212 | "|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}" | |
213 | "|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|" | |
214 | "[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|" | |
215 | "int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2" | |
216 | "}))[\:]*([0-9]+)*([/]*($|[a-zA-Z0-9\.\,\?\'\\\+&%\$#\=~_\-]+))" | |
217 | ".*?$", | |
218 | uri) | |
219 | ||
220 | protocol = mregex.group(1) | |
221 | host = mregex.group(4) | |
222 | port = 80 | |
223 | if protocol == 'https': | |
224 | port = 443 | |
225 | if mregex.group(11) is not None: | |
226 | port = mregex.group(11) | |
227 | ||
228 | try: | |
229 | params = [i.split('=')[0] | |
230 | for i in uri.split('?')[1].split('&')] | |
231 | except Exception as e: | |
232 | params = '' | |
233 | ||
234 | item = { | |
235 | 'uri': uri, | |
236 | 'params': ', '.join(params), | |
237 | 'host': host, | |
238 | 'protocol': protocol, | |
239 | 'port': port | |
240 | } | |
241 | self.items.append(item) | |
242 | ||
243 | def get_text_from_subnode(self, subnode_xpath_expr): | |
244 | """ | |
245 | Finds a subnode in the host node and the retrieves a value from it. | |
246 | ||
247 | @return An attribute value | |
248 | """ | |
249 | sub_node = self.node.find(subnode_xpath_expr) | |
250 | if sub_node is not None: | |
251 | return sub_node.text | |
252 | ||
253 | return None | |
254 | ||
255 | ||
256 | class ZapPlugin(PluginXMLFormat): | |
257 | """ | |
258 | Example plugin to parse zap output. | |
259 | """ | |
260 | ||
261 | def __init__(self): | |
262 | super().__init__() | |
263 | self.identifier_tag = "OWASPZAPReport" | |
264 | self.id = "Zap" | |
265 | self.name = "Zap XML Output Plugin" | |
266 | self.plugin_version = "0.0.3" | |
267 | self.version = "2.4.3" | |
268 | self.framework_version = "1.0.0" | |
269 | self.options = None | |
270 | self._current_output = None | |
271 | self.target = None | |
272 | self._command_regex = re.compile(r'^(zap|sudo zap|\.\/zap).*?') | |
273 | ||
274 | def parseOutputString(self, output, debug=False): | |
275 | """ | |
276 | This method will discard the output the shell sends, it will read it | |
277 | from the xml where it expects it to be present. | |
278 | ||
279 | NOTE: if 'debug' is true then it is being run from a test case and the | |
280 | output being sent is valid. | |
281 | """ | |
282 | ||
283 | parser = ZapXmlParser(output) | |
284 | ||
285 | for site in parser.sites: | |
286 | ||
287 | host = [] | |
288 | if site.host != site.ip: | |
289 | host = [site.host] | |
290 | ||
291 | h_id = self.createAndAddHost(site.ip) | |
292 | ||
293 | i_id = self.createAndAddInterface( | |
294 | h_id, | |
295 | site.ip, | |
296 | ipv4_address=site.ip, | |
297 | hostname_resolution=host | |
298 | ) | |
299 | ||
300 | s_id = self.createAndAddServiceToInterface( | |
301 | h_id, | |
302 | i_id, | |
303 | "http", | |
304 | "tcp", | |
305 | ports=[site.port], | |
306 | status='open' | |
307 | ) | |
308 | ||
309 | for item in site.items: | |
310 | self.createAndAddVulnWebToService( | |
311 | h_id, | |
312 | s_id, | |
313 | item.name, | |
314 | item.desc, | |
315 | website=site.host, | |
316 | severity=item.severity, | |
317 | path=item.items[0]['uri'], | |
318 | params=item.items[0]['params'], | |
319 | request=item.requests, | |
320 | ref=item.ref, | |
321 | resolution=item.resolution | |
322 | ) | |
323 | ||
324 | del parser | |
325 | ||
326 | def processCommandString(self, username, current_path, command_string): | |
327 | return None | |
328 | ||
329 | def setHost(self): | |
330 | pass | |
331 | ||
332 | ||
333 | def createPlugin(): | |
334 | return ZapPlugin() | |
335 | ||
336 | ||
337 | if __name__ == "__main__": | |
338 | import sys | |
339 | if len(sys.argv) == 2: | |
340 | report_file = sys.argv[1] | |
341 | if os.path.isfile(report_file): | |
342 | plugin = createPlugin() | |
343 | plugin.processReport(report_file) | |
344 | print(plugin.get_json()) | |
345 | else: | |
346 | print(f"Report not found: {report_file}") | |
347 | else: | |
348 | print(f"USAGE {sys.argv[0]} REPORT_FILE") |
0 | <?xml version="1.0" encoding="UTF-8"?><report> | |
1 | Report generated at Tue, 12 Jul 2011 08:32:22. | |
2 | <alertitem> | |
3 | <pluginid>40000</pluginid> | |
4 | <alert>Cookie set without HttpOnly flag</alert> | |
5 | <riskcode>1</riskcode> | |
6 | <reliability>2</reliability> | |
7 | <riskdesc>Low (Warning)</riskdesc> | |
8 | <desc>A cookie has been set without the HttpOnly flag, which means that the cookie can be accessed by JavaScript. If a malicious script can be run on this page then the cookie will be accessible and can be transmitted to another site. If this is a session cookie then session hijacking may be possible. | |
9 | </desc> | |
10 | <uri>http://192.168.1.100/</uri> | |
11 | <param>ASPSESSIONIDQSDRBCRQ=EEFHJOACLHOKLJHFNAFBBECK; path=/</param> | |
12 | <otherinfo/> | |
13 | <uri>http://www.web3.com.ar/ServFotoPorNoticia.asp</uri> | |
14 | <param>ASPSESSIONIDCQATADBB=LMFPHGLBHIIEDFILGFJEJNGE; path=/</param> | |
15 | <otherinfo/> | |
16 | <uri>http://www.web1.com.ar/acceso/include/valida.asp</uri> | |
17 | <param>ASPSESSIONIDSCBABSTB=MNPOKADDPAIDCDNBPGFDHGBF; path=/</param> | |
18 | <otherinfo/> | |
19 | <uri>http://www.web3.com.ar/files/</uri> | |
20 | <param>ASPSESSIONIDCSCTCABB=HFCNOPJBMNJEAHDHMCKAHOBN; path=/</param> | |
21 | <otherinfo/> | |
22 | <uri>http://www.web2.com.ar/acceso/include/valida.asp</uri> | |
23 | <param>ASPSESSIONIDQAASDACB=HADJFCIBOIANGBGNAOIDBGIL; path=/</param> | |
24 | <otherinfo/> | |
25 | <uri>http://www.web3.com.ar/</uri> | |
26 | <param>ASPSESSIONIDSABQACDB=PAJBMJHBLOFELCIKBNLAAKKJ; path=/</param> | |
27 | <otherinfo/> | |
28 | <solution>Ensure that the HttpOnly flag is set for all cookies. | |
29 | </solution> | |
30 | <reference>www.owasp.org/index.php/HttpOnly | |
31 | </reference> | |
32 | </alertitem> | |
33 | <alertitem> | |
34 | <pluginid>40001</pluginid> | |
35 | <alert>Password Autocomplete in browser</alert> | |
36 | <riskcode>1</riskcode> | |
37 | <reliability>2</reliability> | |
38 | <riskdesc>Low (Warning)</riskdesc> | |
39 | <desc>AUTOCOMPLETE attribute is not disabled in HTML FORM/INPUT element containing password type input. Passwords may be stored in browsers and retrieved. | |
40 | </desc> | |
41 | <uri>http://192.168.1.100/</uri> | |
42 | <param>input</param> | |
43 | <otherinfo/> | |
44 | <uri>http://www.web3.com.ar/</uri> | |
45 | <param>input</param> | |
46 | <otherinfo/> | |
47 | <uri>http://www.web3.com.ar/default.asp?errsession=1</uri> | |
48 | <param>input</param> | |
49 | <otherinfo/> | |
50 | <uri>http://www.web3.com.ar/</uri> | |
51 | <param>input</param> | |
52 | <otherinfo/> | |
53 | <uri>http://www.web3.com.ar/</uri> | |
54 | <param>input</param> | |
55 | <otherinfo/> | |
56 | <uri>http://www.web2.com.ar/dealers.htm</uri> | |
57 | <param>input</param> | |
58 | <otherinfo/> | |
59 | <uri>http://www.web3.com.ar/</uri> | |
60 | <param>input</param> | |
61 | <otherinfo/> | |
62 | <solution>Turn off AUTOCOMPLETE attribute in form or individual input elements containing password by using AUTOCOMPLETE='OFF' | |
63 | </solution> | |
64 | <reference>http://msdn.microsoft.com/library/default.asp?url=/workshop/author/forms/autocomplete_ovr.asp | |
65 | </reference> | |
66 | </alertitem> | |
67 | <alertitem> | |
68 | <pluginid>40003</pluginid> | |
69 | <alert>Cross site scripting</alert> | |
70 | <riskcode>3</riskcode> | |
71 | <reliability>2</reliability> | |
72 | <riskdesc>High (Warning)</riskdesc> | |
73 | <desc>Cross-site scripting or HTML injection is possible. Malicious script may be injected into the browser which appeared to be genuine content from the original site. These scripts can be used to execute arbitrary code or steal customer sensitive information such as user password or cookies. | |
74 | Very often this is in the form of a hyperlink with the injected script embeded in the query strings. However, XSS is possible via FORM POST data, cookies, user data sent from another user or shared data retrieved from database. | |
75 | Currently this check does not verify XSS from cookie or database. They should be checked manually if the application retrieve database records from another user's input. | |
76 | </desc> | |
77 | <uri>http://www.web3.com.ar/Mes.asp?hhFrm=frm&hhDia=DiaF&hhMes=MesF&hhAnno=%3CSCRIPT%3Ealert(%22OWASP%20ZAP%22);%3C/SCRIPT%3E</uri> | |
78 | <param>hhAnno=<SCRIPT>alert("OWASP ZAP");</SCRIPT></param> | |
79 | <otherinfo/> | |
80 | <uri>http://www.web3.com.ar/Mes.asp?hhFrm=frm&hhDia=DiaF&hhMes=%3CSCRIPT%3Ealert(%22OWASP%20ZAP%22);%3C/SCRIPT%3E&hhAnno=AnnoF</uri> | |
81 | <param>hhMes=<SCRIPT>alert("OWASP ZAP");</SCRIPT></param> | |
82 | <otherinfo/> | |
83 | <uri>http://www.web3.com.ar/Mes.asp?hhFrm=frm&hhDia=%3CSCRIPT%3Ealert(%22OWASP%20ZAP%22);%3C/SCRIPT%3E&hhMes=MesF&hhAnno=AnnoF</uri> | |
84 | <param>hhDia=<SCRIPT>alert("OWASP ZAP");</SCRIPT></param> | |
85 | <otherinfo/> | |
86 | <uri>http://www.web3.com.ar/Mes.asp?hhFrm=%3CSCRIPT%3Ealert(%22OWASP%20ZAP%22);%3C/SCRIPT%3E&hhDia=DiaF&hhMes=MesF&hhAnno=AnnoF</uri> | |
87 | <param>hhFrm=<SCRIPT>alert("OWASP ZAP");</SCRIPT></param> | |
88 | <otherinfo/> | |
89 | <solution>Do not trust client side input even if there is client side validation. Sanitize potentially danger characters in the server side. Very often filtering the <, >, " characters prevented injected script to be executed in most cases. However, sometimes other danger meta-characters such as ' , (, ), /, &, ; etc are also needed. | |
90 | In addition (or if these characters are needed), HTML encode meta-characters in the response. For example, encode < as &lt; | |
91 | ||
92 | </solution> | |
93 | <reference>The OWASP guide at http://www.owasp.org/documentation/guide | |
94 | http://www.technicalinfo.net/papers/CSS.html | |
95 | http://www.cgisecurity.org/articles/xss-faq.shtml | |
96 | http://www.cert.org/tech_tips/malicious_code_FAQ.html | |
97 | http://sandsprite.com/Sleuth/papers/RealWorld_XSS_1.html | |
98 | ||
99 | </reference> | |
100 | </alertitem> | |
101 | <alertitem> | |
102 | <pluginid>40004</pluginid> | |
103 | <alert>Cross site scripting without brackets</alert> | |
104 | <riskcode>3</riskcode> | |
105 | <reliability>1</reliability> | |
106 | <riskdesc>High (Suspicious)</riskdesc> | |
107 | <desc>Cross-site scripting or HTML injection is possible without '<' and '>'. Malicious script may be injected into the browser which appeared to be genuine content from the original site. These scripts can be used to execute arbitrary code or steal customer sensitive information such as user password or cookies. | |
108 | Very often this is in the form of a hyperlink with the injected script embeded in the query strings. However, XSS is possible via FORM POST data, cookies, user data sent from another user or shared data retrieved from database. | |
109 | Currently this check does not verify XSS from cookie or database. They should be checked manually if the application retrieve database records from another user's input. | |
110 | </desc> | |
111 | <uri>http://www.web3.com.ar/Mes.asp?hhFrm=frm&hhDia=DiaF&hhMes=MesF&hhAnno=paros%22%20style=%22background:url(javascript:alert('OWASP%20ZAP'))</uri> | |
112 | <param>hhAnno=paros" style="background:url(javascript:alert('OWASP ZAP'))</param> | |
113 | <otherinfo/> | |
114 | <uri>http://www.web3.com.ar/Mes.asp?hhFrm=frm&hhDia=DiaF&hhMes=paros%22%20style=%22background:url(javascript:alert('OWASP%20ZAP'))&hhAnno=%3CSCRIPT%3Ealert(%22OWASP%20ZAP%22);%3C/SCRIPT%3E</uri> | |
115 | <param>hhMes=paros" style="background:url(javascript:alert('OWASP ZAP'))</param> | |
116 | <otherinfo/> | |
117 | <uri>http://www.web3.com.ar/Mes.asp?hhFrm=frm&hhDia=paros%22%20style=%22background:url(javascript:alert('OWASP%20ZAP'))&hhMes=MesF&hhAnno=%3CSCRIPT%3Ealert(%22OWASP%20ZAP%22);%3C/SCRIPT%3E</uri> | |
118 | <param>hhDia=paros" style="background:url(javascript:alert('OWASP ZAP'))</param> | |
119 | <otherinfo/> | |
120 | <uri>http://www.web3.com.ar/Mes.asp?hhFrm=paros%22%20style=%22background:url(javascript:alert('OWASP%20ZAP'))&hhDia=DiaF&hhMes=MesF&hhAnno=%3CSCRIPT%3Ealert(%22OWASP%20ZAP%22);%3C/SCRIPT%3E</uri> | |
121 | <param>hhFrm=paros" style="background:url(javascript:alert('OWASP ZAP'))</param> | |
122 | <otherinfo/> | |
123 | <solution>Do not trust client side input even if there is client side validation. Sanitize potentially danger characters in the server side. Very often filtering the <, >, " characters prevented injected script to be executed in most cases. However, sometimes other danger meta-characters such as ' , (, ), /, &, ; etc are also needed. | |
124 | In addition (or if these characters are needed), HTML encode meta-characters in the response. For example, encode < as &lt; | |
125 | ||
126 | </solution> | |
127 | <reference>The OWASP guide at http://www.owasp.org/documentation/guide | |
128 | http://www.technicalinfo.net/papers/CSS.html | |
129 | http://www.cgisecurity.org/articles/xss-faq.shtml | |
130 | http://www.cert.org/tech_tips/malicious_code_FAQ.html | |
131 | http://sandsprite.com/Sleuth/papers/RealWorld_XSS_1.html | |
132 | ||
133 | </reference> | |
134 | </alertitem> | |
135 | <alertitem> | |
136 | <pluginid>40030</pluginid> | |
137 | <alert>SQL Injection</alert> | |
138 | <riskcode>3</riskcode> | |
139 | <reliability>2</reliability> | |
140 | <riskdesc>High (Warning)</riskdesc> | |
141 | <desc>SQL injection is possible. User parameters submitted will be formulated into a SQL query for database processing. If the query is built by simple 'string concatenation', it is possible to modify the meaning of the query by carefully crafting the parameters. Depending on the access right and type of database used, tampered query can be used to retrieve sensitive information from the database or execute arbitrary code. MS SQL and PostGreSQL, which supports multiple statements, may be exploited if the database access right is more powerful. | |
142 | This can occur in URL query strings, POST paramters or even cookies. Currently check on cookie is not supported by Paros. You should check SQL injection manually as well as some blind SQL injection areas cannot be discovered by this check. | |
143 | </desc> | |
144 | <uri>http://www.web3.com.ar/buscador.asp</uri> | |
145 | <param>hId=&hAreturn=&hAccion=OK&txtBuscar=test&x=0&y=0%27+AND+%271%27%3D%271</param> | |
146 | <otherinfo/> | |
147 | <uri>http://www.web3.com.ar/buscador.asp</uri> | |
148 | <param>hId=&hAreturn=&hAccion=OK%22+OR+%221%22%3D%221&txtBuscar=test&x=0&y=0</param> | |
149 | <otherinfo/> | |
150 | <solution>Do not trust client side input even if there is client side validation. In general, If the input string is numeric, type check it. | |
151 | If the application used JDBC, use PreparedStatement or CallableStatement with parameters passed by '?' | |
152 | If the application used ASP, use ADO Command Objects with strong type checking and parameterized query. | |
153 | If stored procedure or bind variables can be used, use it for parameter passing into query. Do not just concatenate string into query in the stored procedure! | |
154 | Do not create dynamic SQL query by simple string concatentation. | |
155 | Use minimum database user privilege for the application. This does not eliminate SQL injection but minimize its damage. Eg if the application require reading one table only, grant such access to the application. Avoid using 'sa' or 'db-owner'. | |
156 | ||
157 | </solution> | |
158 | <reference>The OWASP guide at http://www.owasp.org/documentation/guide | |
159 | http://www.sqlsecurity.com/DesktopDefault.aspx?tabid=23 | |
160 | http://www.spidynamics.com/whitepapers/WhitepaperSQLInjection.pdf | |
161 | For Oracle database, refer to http://www.integrigy.com/info/IntegrigyIntrotoSQLInjectionAttacks.pdf | |
162 | ||
163 | </reference> | |
164 | </alertitem> | |
165 | </report>⏎ |
Binary diff not shown
0 | from setuptools import setup, find_packages | |
1 | from re import search | |
2 | ||
3 | with open('faraday_plugins/__init__.py', 'rt', encoding='utf8') as f: | |
4 | version = search(r'__version__ = \'(.*?)\'', f.read()).group(1) | |
5 | ||
6 | ||
7 | install_requires = [ | |
8 | 'Click', | |
9 | 'simplejson', | |
10 | 'requests', | |
11 | ] | |
12 | ||
13 | ||
14 | setup( | |
15 | name='faraday-plugins', | |
16 | version=version, | |
17 | packages=find_packages(include=['faraday_plugins', 'faraday_plugins.*']), | |
18 | url='', | |
19 | license='', | |
20 | author='Faradaysec', | |
21 | author_email='', | |
22 | description='', | |
23 | include_package_data=True, | |
24 | install_requires=install_requires, | |
25 | ) |
0 | import os | |
1 | ||
2 | import json | |
3 | import pytest | |
4 | from faraday_plugins.plugins.manager import PluginsManager, ReportAnalyzer | |
5 | from faraday_plugins.plugins.plugin import PluginBase | |
6 | ||
7 | BLACK_LIST = [ | |
8 | 'LICENSE', | |
9 | 'README.md', | |
10 | '.gitignore', | |
11 | '.gitkeep', | |
12 | ] | |
13 | ||
14 | def list_report_files(): | |
15 | report_filenames = os.walk('./report-collection') | |
16 | ||
17 | for root, directory, filenames in report_filenames: | |
18 | if '.git' in directory: | |
19 | continue | |
20 | for filename in filenames: | |
21 | if filename in BLACK_LIST: | |
22 | continue | |
23 | if '.git' in root: | |
24 | continue | |
25 | yield os.path.join(root, filename) | |
26 | ||
27 | ||
28 | @pytest.mark.parametrize("report_filename", list_report_files()) | |
29 | def test_autodetection_on_all_report_collection(report_filename): | |
30 | plugins_manager = PluginsManager() | |
31 | analyzer = ReportAnalyzer(plugins_manager) | |
32 | plugin: PluginBase = analyzer.get_plugin(report_filename) | |
33 | assert plugin, report_filename | |
34 | plugin.processReport(report_filename) | |
35 | plugin_json = json.loads(plugin.get_json()) | |
36 | assert "hosts" in plugin_json | |
37 | assert "command" in plugin_json | |
38 | assert len(plugin_json) == 2 |