New upstream version 3.6.0
Sophie Brun
5 years ago
68 | 68 | |
69 | 69 | # Documentation builds |
70 | 70 | doc/_build/ |
71 | scripts/searcher/log/searcher.log | |
72 | =0.54.0 | |
73 | stream.svg | |
74 | scripts/searcher/output/searcher.db | |
75 | .pytest_cache | |
76 | reports/executive/outputs/scope.docx | |
77 | .gitignore |
0 | --- | |
1 | name: Feature request | |
2 | about: Suggest an idea for this | |
3 | ||
4 | --- | |
5 | ||
6 | **What's the problem this feature will solve?** | |
7 | <!-- What are you trying to do, that you are unable to achieve with faraday as it currently stands? --> | |
8 | ||
9 | **Describe the solution you'd like** | |
10 | <!-- Clear and concise description of what you want to happen. --> | |
11 | ||
12 | <!-- Provide examples of real world use cases that this would enable and how it solves the problem described above. --> | |
13 | ||
14 | **Alternative Solutions** | |
15 | <!-- different approach to solving this issue? Please elaborate here. --> | |
16 | ||
17 | **Additional context** | |
18 | <!-- Add any other context, links, etc. about the feature here. --> |
0 | --- | |
1 | name: Bug report | |
2 | about: Create a report an issue | |
3 | ||
4 | --- | |
5 | ||
6 | Please search the [Wiki](https://github.com/infobyte/faraday/wiki) for a solution before posting a ticket. Use the <strong>“New Support Request”</strong> button to the right of the screen to submit a ticket for technical support. | |
7 | ||
8 | ## Issue Type | |
9 | - Bug Re port | |
10 | - Feature Idea | |
11 | - Documentation Report | |
12 | ||
13 | ||
14 | ## Faraday version | |
15 | ||
16 | Paste the output of the *./faraday.py --version* command | |
17 | ||
18 | ## Component Name | |
19 | ||
20 | If you know where the problem lays indicate it: | |
21 | WebGui/GTKGui/Plugin/Console/Continuous Scanning/Etc. | |
22 | ||
23 | ## Steps to reproduce | |
24 | ||
25 | Provide detailed steps on how the issue happened so we can try to reproduce it. If the issue is random, please provide as much information as possible. | |
26 | ||
27 | ## Expected results | |
28 | ||
29 | What did you expect to happen when following the steps above? | |
30 | ||
31 | ### Debugging tracebacks (current results) | |
32 | ||
33 | Try to reproduce the bug with the server and/or gtk client in debug mode and check the logs for the ERROR string. | |
34 | Add here any errors you find while running in debug mode or, if possible, Faraday’s log files (located at *$HOME/.faraday/logs/*). | |
35 | ||
36 | If you need help on how to execute in debug mode [click here for more information](https://github.com/infobyte/faraday/wiki/troubleshooting). | |
37 | ||
38 | Please attach the result of: | |
39 | ||
40 | pip freeze > requirements_freeze.txt | |
41 | ||
42 | ### Screenshots | |
43 | ||
44 | If you don't find anything on the logs, please provide screenshots of the error. | |
45 | ||
46 | ## Environment information | |
47 | ||
48 | ### Configuration files | |
49 | ||
50 | Mention any settings you have changed/added/removed. | |
51 | ||
52 | ### Reports/Extra data | |
53 | ||
54 | If you are having issues with plugins, please attach relevant files if possible. | |
55 | (strip your reports of all sensitive information beforehand). | |
56 | ||
57 | ### OS | |
58 | ||
59 | Provide information on your operating system. Example: | |
60 | ||
61 | $ cat /etc/lsb-release | |
62 | DISTRIB_ID=Ubuntu | |
63 | DISTRIB_RELEASE=16.10 | |
64 | DISTRIB_CODENAME=yakkety | |
65 | DISTRIB_DESCRIPTION="Ubuntu 16.10" |
0 | variables: | |
1 | TZ: "America/New_York" | |
2 | # Configure postgres service (https://hub.docker.com/_/postgres/) | |
3 | POSTGRES_DB: custom_db | |
4 | POSTGRES_USER: custom_user | |
5 | POSTGRES_PASSWORD: custom_pass | |
6 | PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" | |
7 | ||
8 | cache: | |
9 | paths: | |
10 | - .cache/pip | |
11 | ||
12 | stages: | |
13 | - pre_testing | |
14 | - testing | |
15 | - post_testing | |
16 | ||
17 | services: | |
18 | - postgres:latest | |
19 | ||
20 | closure_compiler: | |
21 | image: | |
22 | name: jborza/closure-compiler | |
23 | entrypoint: ["/bin/sh", "-c"] | |
24 | ||
25 | stage: pre_testing | |
26 | script: | |
27 | - /opt/cc.sh server/www/scripts | |
28 | ||
29 | merge_conflict_check: | |
30 | image: python:3 | |
31 | stage: pre_testing | |
32 | allow_failure: true | |
33 | script: | |
34 | - git config --global user.email "[email protected]" | |
35 | - git config --global user.name "Mergerbot" | |
36 | - python3 merge-conflict-detector.py | |
37 | ||
38 | pylint: | |
39 | image: registry.gitlab.com/faradaysec/faraday/faraday_testing_base # I just need an image with python-dev and python-pip | |
40 | stage: pre_testing | |
41 | script: | |
42 | - pip install pylint anybadge | |
43 | - pylint server |tee pylint.txt || true | |
44 | - score=$(sed -n 's/^Your code has been rated at \([-0-9.]*\)\/.*/\1/p' pylint.txt) | |
45 | - anybadge --label pylint --value=$score --file pylint.svg 4=red 6=orange 8=yellow 10=green | |
46 | artifacts: | |
47 | paths: | |
48 | - pylint.svg | |
49 | ||
50 | postgresql_test: | |
51 | image: registry.gitlab.com/faradaysec/faraday/faraday_testing_base | |
52 | stage: testing | |
53 | coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/' | |
54 | script: | |
55 | - pip install virtualenv | |
56 | - virtualenv -p python2 faraday_venv | |
57 | - source faraday_venv/bin/activate | |
58 | - pip install --upgrade -r requirements_server.txt | |
59 | - pip install --upgrade responses pytest-xdist pytest-cov | |
60 | - pip install --upgrade -r requirements_dev.txt | |
61 | - mkdir -p ~/.faraday/config | |
62 | - cp test_cases/data/server.ini ~/.faraday/config | |
63 | - sed -i 's/mapped_table/persist_selectable/' faraday_venv/lib/python2.7/site-packages/flask_sqlalchemy/__init__.py # TODO remove when flask_sqlalchemy fixes the issue | |
64 | - pytest test_cases -v --cov=server --connection-string=postgresql+psycopg2://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres/$POSTGRES_DB | |
65 | artifacts: | |
66 | when: on_failure | |
67 | paths: | |
68 | - ~/.faraday/logs/faraday-server.log | |
69 | ||
70 | sqlite_test: | |
71 | image: registry.gitlab.com/faradaysec/faraday/faraday_testing_base | |
72 | stage: testing | |
73 | coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/' | |
74 | script: | |
75 | - pip install virtualenv | |
76 | - virtualenv -p python2 faraday_venv | |
77 | - source faraday_venv/bin/activate | |
78 | - pip install --upgrade -r requirements_server.txt | |
79 | - pip install --upgrade responses pytest-xdist pytest-cov | |
80 | - pip install --upgrade -r requirements_dev.txt | |
81 | - mkdir -p ~/.faraday/config | |
82 | - cp test_cases/data/server.ini ~/.faraday/config | |
83 | - sed -i 's/mapped_table/persist_selectable/' faraday_venv/lib/python2.7/site-packages/flask_sqlalchemy/__init__.py # TODO remove when flask_sqlalchemy fixes the issue | |
84 | - pytest test_cases -v --cov=server --color=yes | |
85 | ||
86 | generate_release_file: | |
87 | image: python:3 | |
88 | stage: post_testing | |
89 | allow_failure: true | |
90 | script: | |
91 | - apt-get update -qy | |
92 | - apt-get install -y python-dev python-pip | |
93 | - pip install packaging | |
94 | - cd CHANGELOG && python3 changelog.py | |
95 | artifacts: | |
96 | paths: | |
97 | - CHANGELOG/RELEASE.md | |
98 | only: | |
99 | variables: | |
100 | - $FULL_DOC == "True" | |
101 | - $RELEASE_FILE == "True" | |
102 | - $CI_COMMIT_REF_NAME =~ /^.*\/(dev|master)$/ | |
103 | ||
104 | # This is a test of future test case that will be scheduled | |
105 | i_do_nothing: | |
106 | image: python:3 | |
107 | stage: post_testing | |
108 | allow_failure: true | |
109 | script: | |
110 | - apt-get update -qy | |
111 | - apt-get install -y python-dev python-pip | |
112 | only: | |
113 | variables: | |
114 | - $FULL_TEST == "True" | |
115 | - $I_DO_NOTH_TEST == "True" | |
116 | ||
117 | ||
118 | # This is a test of future test case that will be scheduled | |
119 | i_do_nothing2: | |
120 | image: python:3 | |
121 | stage: post_testing | |
122 | allow_failure: true | |
123 | script: | |
124 | - apt-get update -qy | |
125 | - apt-get install -y python-dev python-pip | |
126 | only: | |
127 | variables: | |
128 | - $FULL_TEST == "True" | |
129 | - $I_DO_NOTH2_TEST == "True" |
0 | Feb 21th, 2019 |
0 | * Fix CSRF (Cross-Site Request Forgery) vulnerability in vulnerability attachments API. | |
1 | This allowed an attacker to upload evidence to vulns. He/she required to know the | |
2 | desired workspace name and vulnerability id so it complicated the things a bit. We | |
3 | classified this vuln as a low impact one. | |
4 | * Readonly and disabled workspaces | |
5 | * Add fields 'impact', 'easeofresolution' and 'policyviolations' to vulnerability_template | |
6 | * Add pagination in 'Command history', 'Last Vulnerabilities', 'Activity logs' into dashboard | |
7 | * Add status_code field to web vulnerability | |
8 | * Preserve selection after bulk edition of vulnerabilities in the Web UI | |
9 | * Faraday's database will be created using UTF-8 encoding | |
10 | * Fix bug of "select a different workspace" from an empty list loop. | |
11 | * Fix bug when creating duplicate custom fields | |
12 | * Fix bug when loading in server.ini with extra configs | |
13 | * Fix `./manage.py command`. It wasn't working since the last schema migration | |
14 | * `./manage.py createsuperuser` command renamed to `./manage.py create-superuser` | |
15 | * Fix bug when non-numeric vulnerability IDs were passed to the attachments API | |
16 | * Fix logic in search exploits | |
17 | * Add ability to 'Searcher' to execute rules in loop with dynamic variables | |
18 | * Send searcher alert with custom mail | |
19 | * Add gitlab-ci.yml file to execute test and pylint on gitlab runner | |
20 | * Fix 500 error when updating services and vulns with specific read-only parameters set | |
21 | * Fix SQLMap plugin to support newer versions of the tool | |
22 | * Improve service's parser for Lynis plugin | |
23 | * Fix bug when parsing URLs in Acunetix reports | |
24 | * Fix and update NetSparker Plugin | |
25 | * Fix bug in nessus plugin. It was trying to create a host without IP. Enabled logs on the server for plugin processing (use --debug) | |
26 | * Fix bug when parsing hostnames in Nessus reports | |
27 | * Fix SSLyze report automatic detection, so reports can be imported from the web ui | |
28 | * Update Dnsmap Plugin |
7 | 7 | New features in the latest update |
8 | 8 | ===================================== |
9 | 9 | |
10 | ||
11 | 3.6 [Feb 21th, 2019]: | |
12 | --- | |
13 | * Fix CSRF (Cross-Site Request Forgery) vulnerability in vulnerability attachments API. | |
14 | This allowed an attacker to upload evidence to vulns. He/she required to know the | |
15 | desired workspace name and vulnerability id so it complicated the things a bit. We | |
16 | classified this vuln as a low impact one. | |
17 | * Readonly and disabled workspaces | |
18 | * Add fields 'impact', 'easeofresolution' and 'policyviolations' to vulnerability_template | |
19 | * Add pagination in 'Command history', 'Last Vulnerabilities', 'Activity logs' into dashboard | |
20 | * Add status_code field to web vulnerability | |
21 | * Preserve selection after bulk edition of vulnerabilities in the Web UI | |
22 | * Faraday's database will be created using UTF-8 encoding | |
23 | * Fix bug of "select a different workspace" from an empty list loop. | |
24 | * Fix bug when creating duplicate custom fields | |
25 | * Fix bug when loading in server.ini with extra configs | |
26 | * Fix `./manage.py command`. It wasn't working since the last schema migration | |
27 | * `./manage.py createsuperuser` command renamed to `./manage.py create-superuser` | |
28 | * Fix bug when non-numeric vulnerability IDs were passed to the attachments API | |
29 | * Fix logic in search exploits | |
30 | * Add ability to 'Searcher' to execute rules in loop with dynamic variables | |
31 | * Send searcher alert with custom mail | |
32 | * Add gitlab-ci.yml file to execute test and pylint on gitlab runner | |
33 | * Fix 500 error when updating services and vulns with specific read-only parameters set | |
34 | * Fix SQLMap plugin to support newer versions of the tool | |
35 | * Improve service's parser for Lynis plugin | |
36 | * Fix bug when parsing URLs in Acunetix reports | |
37 | * Fix and update NetSparker Plugin | |
38 | * Fix bug in nessus plugin. It was trying to create a host without IP. Enabled logs on the server for plugin processing (use --debug) | |
39 | * Fix bug when parsing hostnames in Nessus reports | |
40 | * Fix SSLyze report automatic detection, so reports can be imported from the web ui | |
41 | * Update Dnsmap Plugin | |
10 | 42 | |
11 | 43 | 3.5 [Jan 16th, 2019]: |
12 | 44 | --- |
7 | 7 | New features in the latest update |
8 | 8 | ===================================== |
9 | 9 | |
10 | ||
11 | 3.6 [Feb 21th, 2019]: | |
12 | --- | |
13 | * Fix CSRF (Cross-Site Request Forgery) vulnerability in vulnerability attachments API. | |
14 | This allowed an attacker to upload evidence to vulns. He/she required to know the | |
15 | desired workspace name and vulnerability id so it complicated the things a bit. We | |
16 | classified this vuln as a low impact one. | |
17 | * Readonly and disabled workspaces | |
18 | * Add fields 'impact', 'easeofresolution' and 'policyviolations' to vulnerability_template | |
19 | * Add pagination in 'Command history', 'Last Vulnerabilities', 'Activity logs' into dashboard | |
20 | * Add status_code field to web vulnerability | |
21 | * Preserve selection after bulk edition of vulnerabilities in the Web UI | |
22 | * Faraday's database will be created using UTF-8 encoding | |
23 | * Fix bug of "select a different workspace" from an empty list loop. | |
24 | * Fix bug when creating duplicate custom fields | |
25 | * Fix bug when loading in server.ini with extra configs | |
26 | * Fix `./manage.py command`. It wasn't working since the last schema migration | |
27 | * `./manage.py createsuperuser` command renamed to `./manage.py create-superuser` | |
28 | * Fix bug when non-numeric vulnerability IDs were passed to the attachments API | |
29 | * Fix logic in search exploits | |
30 | * Add ability to 'Searcher' to execute rules in loop with dynamic variables | |
31 | * Send searcher alert with custom mail | |
32 | * Add gitlab-ci.yml file to execute test and pylint on gitlab runner | |
33 | * Fix 500 error when updating services and vulns with specific read-only parameters set | |
34 | * Fix SQLMap plugin to support newer versions of the tool | |
35 | * Improve service's parser for Lynis plugin | |
36 | * Fix bug when parsing URLs in Acunetix reports | |
37 | * Fix and update NetSparker Plugin | |
38 | * Fix bug in nessus plugin. It was trying to create a host without IP. Enabled logs on the server for plugin processing (use --debug) | |
39 | * Fix bug when parsing hostnames in Nessus reports | |
40 | * Fix SSLyze report automatic detection, so reports can be imported from the web ui | |
41 | * Update Dnsmap Plugin | |
10 | 42 | |
11 | 43 | 3.5 [Jan 16th, 2019]: |
12 | 44 | --- |
260 | 260 | try: |
261 | 261 | models.create_host(WORKSPACE, host) |
262 | 262 | except Exception as ex: |
263 | import ipdb; ipdb.set_trace() | |
263 | print(ex) | |
264 | 264 | host = models.get_host(WORKSPACE, ip=host.getName()) |
265 | 265 | |
266 | 266 | if service is not None: |
63 | 63 | |
64 | 64 | |
65 | 65 | DEFAULT_XML = os.path.dirname(__file__) + "/default.xml" |
66 | DEFAULT_SERVER_INI = os.path.join(os.path.dirname(__file__), "..", "server", "default.ini") | |
66 | 67 | |
67 | 68 | |
68 | 69 | class Configuration: |
647 | 648 | def getInstanceConfiguration(): |
648 | 649 | global the_config |
649 | 650 | if the_config is None: |
651 | faraday_dir = os.path.expanduser("~/.faraday") | |
652 | if not os.path.exists(faraday_dir): | |
653 | os.mkdir(faraday_dir) | |
650 | 654 | config_dir = os.path.expanduser("~/.faraday/config") |
651 | 655 | if not os.path.exists(config_dir): |
652 | 656 | os.mkdir(config_dir) |
657 | ||
658 | faraday_server_config = os.path.expanduser("~/.faraday/config/server.ini") | |
659 | if not os.path.isfile(faraday_server_config): | |
660 | shutil.copy(DEFAULT_SERVER_INI, faraday_server_config) | |
661 | ||
653 | 662 | faraday_user_config = os.path.expanduser("~/.faraday/config/user.xml") |
654 | 663 | if not os.path.isfile(faraday_user_config): |
655 | 664 | shutil.copy(DEFAULT_XML, faraday_user_config) |
665 | ||
656 | 666 | if os.path.exists(os.path.expanduser("~/.faraday/config/user.xml")): |
657 | 667 | the_config = Configuration(os.path.expanduser("~/.faraday/config/user.xml")) |
658 | 668 | else: |
1 | 1 | <faraday> |
2 | 2 | |
3 | 3 | <appname>Faraday - Penetration Test IDE</appname> |
4 | <version>3.5.0</version> | |
4 | <version>3.6.0</version> | |
5 | 5 | <debug_status>0</debug_status> |
6 | 6 | <font>-Misc-Fixed-medium-r-normal-*-12-100-100-100-c-70-iso8859-1</font> |
7 | 7 | <home_path>~/</home_path> |
19 | 19 | from utils import dependencies |
20 | 20 | from utils.user_input import query_yes_no |
21 | 21 | from faraday import FARADAY_BASE |
22 | from utils.logs import setUpLogger | |
22 | 23 | from alembic.script import ScriptDirectory |
23 | 24 | from alembic.config import Config |
24 | 25 | from alembic.migration import MigrationContext |
138 | 139 | if head_revision != context.get_current_revision(): |
139 | 140 | print('--' * 20) |
140 | 141 | print('Missing migrations, please execute: \n\n') |
141 | print('python manage.py migrate --upgrade head') | |
142 | print('python manage.py migrate') | |
142 | 143 | sys.exit(1) |
143 | 144 | |
144 | 145 | def main(): |
145 | 146 | os.chdir(FARADAY_BASE) |
147 | check_alembic_version() | |
146 | 148 | check_postgresql() |
147 | check_alembic_version() | |
148 | 149 | parser = argparse.ArgumentParser() |
149 | 150 | parser.add_argument('--ssl', action='store_true', help='enable HTTPS') |
150 | 151 | parser.add_argument('--debug', action='store_true', help='run Faraday Server in debug mode') |
164 | 165 | version='Faraday v{version}'.format(version=f_version)) |
165 | 166 | |
166 | 167 | args = parser.parse_args() |
168 | setUpLogger(args.debug) | |
167 | 169 | |
168 | 170 | if args.debug: |
169 | 171 | server.utils.logger.set_logging_level(server.config.DEBUG) |
168 | 168 | @click.option('--email', prompt=True, callback=validate_email) |
169 | 169 | @click.option('--password', prompt=True, hide_input=True, |
170 | 170 | confirmation_prompt=True) |
171 | def createsuperuser(username, email, password): | |
171 | def create_superuser(username, email, password): | |
172 | 172 | with app.app_context(): |
173 | 173 | if db.session.query(User).filter_by(active=True).count() > 0: |
174 | 174 | print("Can't create more users. The comumunity edition only allows one user. Please contact support for further information.") |
237 | 237 | cli.add_command(initdb) |
238 | 238 | cli.add_command(import_from_couchdb) |
239 | 239 | cli.add_command(database_schema) |
240 | cli.add_command(createsuperuser) | |
240 | cli.add_command(create_superuser) | |
241 | 241 | cli.add_command(sql_shell) |
242 | 242 | cli.add_command(status_check) |
243 | 243 | cli.add_command(create_tables) |
381 | 381 | return "Core Impact" |
382 | 382 | elif tag == "NexposeReport": |
383 | 383 | return "NexposeFull" |
384 | elif tag == "ASSET_DATA_REPORT" or "SCAN": | |
384 | elif tag in ("ASSET_DATA_REPORT", "SCAN"): | |
385 | 385 | return "Qualysguard" |
386 | 386 | elif tag == "scanJob": |
387 | 387 | return "Retina" |
395 | 395 | return "Lynis" |
396 | 396 | elif tag == "reconng": |
397 | 397 | return "Reconng" |
398 | elif tag == "document": | |
399 | if re.search("SSLyzeVersion", output) is not None: | |
400 | return "Sslyze" | |
398 | 401 | else: |
399 | 402 | return None |
0 | #!/usr/bin/env python3 | |
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 | ''' | |
7 | Internal script used to detect merge conflicts to branch with | |
8 | our propiertary code. Not useful if you don't have access to | |
9 | the code of Faraday Professional or Faraday Corporate | |
10 | ''' | |
11 | ||
12 | import os | |
13 | import re | |
14 | import sys | |
15 | import subprocess | |
16 | import logging | |
17 | import argparse | |
18 | from contextlib import contextmanager | |
19 | from tempfile import mkdtemp | |
20 | from shutil import rmtree | |
21 | ||
22 | VERSIONS = ['white', 'pink', 'black'] | |
23 | BRANCH_FORMAT = 'origin/{}/dev' | |
24 | ||
25 | @contextmanager | |
26 | def chdir(directory): | |
27 | """Context manager to work in the specified directory""" | |
28 | current = os.getcwd() | |
29 | os.chdir(directory) | |
30 | yield | |
31 | os.chdir(current) | |
32 | ||
33 | @contextmanager | |
34 | def temp_worktree(branch=None): | |
35 | """Context manager that creates a temporal worktree and | |
36 | changes the current working directory, and when finished | |
37 | removes the dir and runs a git worktree prune""" | |
38 | directory = mkdtemp() | |
39 | cmd = ["git", "worktree", "add", directory] | |
40 | if branch is not None: | |
41 | cmd.append(branch) | |
42 | subprocess.check_output(cmd) | |
43 | with chdir(directory): | |
44 | yield | |
45 | rmtree(directory) | |
46 | subprocess.check_output(['git', 'worktree', 'prune']) | |
47 | ||
48 | def check_merge(dst_branch, cur_branch='HEAD'): | |
49 | """Return a boolean indicating if the merge from cur_branch | |
50 | to dst_branch will merge without causing conflicts that need | |
51 | manual resolution""" | |
52 | # https://stackoverflow.com/questions/501407/is-there-a-git-merge-dry-run-option | |
53 | with temp_worktree(dst_branch): | |
54 | exit_code = subprocess.call( | |
55 | ['git', 'merge', '--no-commit', '--no-ff', cur_branch]) | |
56 | # Use call because it will have exit code 128 when there is nothing to | |
57 | # abort | |
58 | subprocess.call(['git', 'merge', '--abort']) | |
59 | return exit_code == 0 | |
60 | ||
61 | ||
62 | def get_current_branch(): | |
63 | """Return the current branch of the current workspace""" | |
64 | # https://stackoverflow.com/questions/6245570/how-to-get-the-current-branch-name-in-git | |
65 | branch = subprocess.check_output( | |
66 | ['git', 'rev-parse', '--abbrev-ref', 'HEAD']).decode().strip() | |
67 | if branch == 'HEAD': | |
68 | # Probably in a detached state inside gitlab CI | |
69 | # Fallback to the branch name defined in an env var | |
70 | branch = 'origin/' + os.environ['CI_COMMIT_REF_NAME'] | |
71 | return branch | |
72 | ||
73 | ||
74 | def branch_exists(branch_name): | |
75 | exit_code = subprocess.call( | |
76 | ['git', 'rev-parse', '--verify', '--quiet', branch_name]) | |
77 | if exit_code == 0: | |
78 | return True | |
79 | elif exit_code == 1: | |
80 | return False | |
81 | else: | |
82 | raise ValueError('Error when checking for branch existence') | |
83 | ||
84 | ||
85 | def version_of_branch(branch_name): | |
86 | """ | |
87 | >>> version_of_branch('tkt_white_this_is_not_a_pink_branch') | |
88 | 'white' | |
89 | """ | |
90 | positions = {version: branch_name.find(version) | |
91 | for version in VERSIONS} | |
92 | if all((pos < 0) for pos in positions.values()): | |
93 | # The branch name doesn't contain white, pink or black | |
94 | return | |
95 | positions = {version: pos | |
96 | for (version, pos) in positions.items() | |
97 | if pos >= 0} | |
98 | return min(positions.keys(), key=positions.get) | |
99 | ||
100 | ||
101 | def main(branch): | |
102 | logging.getLogger().setLevel(getattr(logging, args.log_level.upper())) | |
103 | logger = logging # TODO FIXME | |
104 | logger.info('Checking merge conflicts for branch %s', branch) | |
105 | version = version_of_branch(branch) | |
106 | if version is None: | |
107 | logger.error('Unknown version name. Exiting') | |
108 | sys.exit(-1) | |
109 | ||
110 | versions_to_test = VERSIONS[VERSIONS.index(version):] | |
111 | branches_to_test = [] | |
112 | for target_version in versions_to_test: | |
113 | overriden_branch = branch.replace(version, target_version) | |
114 | if target_version != version and \ | |
115 | branch_exists(overriden_branch): | |
116 | branches_to_test.append(overriden_branch) | |
117 | break # Don't test merge to black if has overriden pink branch | |
118 | else: | |
119 | branches_to_test.append(BRANCH_FORMAT.format(target_version)) | |
120 | ||
121 | logging.info('Testing merges in branches %s' % branches_to_test) | |
122 | ||
123 | success = True | |
124 | cur_branch = branch | |
125 | for dst_branch in branches_to_test: | |
126 | result = check_merge(dst_branch, cur_branch) | |
127 | if result: | |
128 | logger.info("Merge into %s succeeded!", dst_branch) | |
129 | else: | |
130 | success = False | |
131 | logger.error("Merge into %s failed :(", dst_branch) | |
132 | print() | |
133 | print() | |
134 | ||
135 | if not success: | |
136 | sys.exit(1) | |
137 | ||
138 | ||
139 | if __name__ == "__main__": | |
140 | parser = argparse.ArgumentParser() | |
141 | parser.add_argument('-b', '--branch', default=get_current_branch()) | |
142 | parser.add_argument('-l', '--log-level', default='debug') | |
143 | args = parser.parse_args() | |
144 | main(args.branch) |
0 | """empty message | |
1 | ||
2 | Revision ID: 2ca03a8feef5 | |
3 | Revises: 8a10ff3926a5 | |
4 | Create Date: 2019-01-15 13:02:21.000699+00:00 | |
5 | ||
6 | """ | |
7 | from alembic import op | |
8 | import sqlalchemy as sa | |
9 | ||
10 | ||
11 | # revision identifiers, used by Alembic. | |
12 | revision = '2ca03a8feef5' | |
13 | down_revision = '8a10ff3926a5' | |
14 | branch_labels = None | |
15 | depends_on = None | |
16 | ||
17 | ||
18 | def upgrade(): | |
19 | op.add_column('workspace', sa.Column('readonly', sa.Boolean(), nullable=False, server_default='False')) | |
20 | ||
21 | ||
22 | def downgrade(): | |
23 | op.drop_column('workspace', 'readonly') |
350 | 350 | Return the server's json response as a dictionary. |
351 | 351 | """ |
352 | 352 | host_properties = get_host_properties(host) |
353 | return server.create_host(workspace_name, command_id, **host_properties) | |
353 | ip = host_properties.pop('ip', None) | |
354 | if not ip: | |
355 | logger.error('Trying to create host without ip') | |
356 | return server.create_host(workspace_name, command_id, ip, **host_properties) | |
354 | 357 | |
355 | 358 | |
356 | 359 | @_ignore_in_changes |
746 | 749 | # getId will wait until the id is not None |
747 | 750 | timeout = 1 |
748 | 751 | retries = 1 |
749 | max_retries = 6 | |
752 | max_retries = 4 | |
750 | 753 | while retries <= max_retries and self.id is None: |
751 | 754 | if timeout >= 8: |
752 | 755 | logger.info('Retrying getID timeout {0}'.format(timeout)) |
20 | 20 | tup = (filt[0],filt[1]) |
21 | 21 | services.append(tup) |
22 | 22 | |
23 | return services⏎ | |
23 | return services | |
24 | ||
25 | def get_all_protocols(): | |
26 | protocols = [ | |
27 | 'ip', | |
28 | 'tcp', | |
29 | 'udp', | |
30 | 'icmp', | |
31 | 'sctp', | |
32 | 'hopopt', | |
33 | 'igmp', | |
34 | 'ggp', | |
35 | 'ip-encap', | |
36 | 'st', | |
37 | 'egp', | |
38 | 'igp', | |
39 | 'pup', | |
40 | 'hmp', | |
41 | 'xns-idp', | |
42 | 'rdp', | |
43 | 'iso-tp4', | |
44 | 'dccp', | |
45 | 'xtp', | |
46 | 'ddp', | |
47 | 'idpr-cmtp', | |
48 | 'ipv6', | |
49 | 'ipv6-route', | |
50 | 'ipv6-frag', | |
51 | 'idrp', | |
52 | 'rsvp', | |
53 | 'gre', | |
54 | 'ipsec-esp', | |
55 | 'ipsec-ah', | |
56 | 'skip', | |
57 | 'ipv6-icmp', | |
58 | 'ipv6-nonxt', | |
59 | 'ipv6-opts', | |
60 | 'rspf cphb', | |
61 | 'vmtp', | |
62 | 'eigrp', | |
63 | 'ospfigp', | |
64 | 'ax.25', | |
65 | 'ipip', | |
66 | 'etherip', | |
67 | 'encap', | |
68 | 'pim', | |
69 | 'ipcomp', | |
70 | 'vrrp', | |
71 | 'l2tp', | |
72 | 'isis', | |
73 | 'fc', | |
74 | 'udplite', | |
75 | 'mpls-in-ip', | |
76 | 'hip', | |
77 | 'shim6', | |
78 | 'wesp', | |
79 | 'rohc', | |
80 | 'mobility-header' | |
81 | ] | |
82 | ||
83 | for item in protocols: | |
84 | yield item |
123 | 123 | |
124 | 124 | def __init__(self, item_node): |
125 | 125 | self.node = item_node |
126 | ||
127 | self.url = self.get_text_from_subnode('StartURL') | |
128 | url_data = urlsplit(self.url) | |
126 | url_data = self.get_url(self.node) | |
129 | 127 | |
130 | 128 | self.protocol = url_data.scheme |
131 | 129 | self.host = url_data.hostname |
162 | 160 | level='ERROR') |
163 | 161 | return None |
164 | 162 | return host |
163 | ||
164 | def get_url(self, node): | |
165 | url = self.get_text_from_subnode('StartURL') | |
166 | url_data = urlsplit(url) | |
167 | if not url_data.scheme: | |
168 | # Getting url from subnode 'Crawler' | |
169 | url_aux = get_attrib_from_subnode(node, 'Crawler', 'StartUrl') | |
170 | url_data = urlsplit(url_aux) | |
171 | ||
172 | return url_data | |
165 | 173 | |
166 | 174 | |
167 | 175 | class Item(object): |
Binary diff not shown
0 | #!/usr/bin/ruby | |
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 | #__author__ = "Francisco Amato" | |
7 | #__copyright__ = "Copyright (c) 2014, Infobyte LLC" | |
8 | #__credits__ = ["Francisco Amato", "Micaela Ranea Sanchez"] | |
9 | #__version__ = "1.4.0" | |
10 | #__maintainer__ = "Francisco Amato" | |
11 | #__email__ = "[email protected]" | |
12 | #__status__ = "Development" | |
13 | ||
14 | require 'java' | |
15 | require "xmlrpc/client" | |
16 | require "pp" | |
17 | ||
18 | ||
19 | PLUGINVERSION="Faraday v1.4 Ruby" | |
20 | #Tested: Burp Professional v1.6.09 | |
21 | ||
22 | XMLRPC::Config.module_eval do | |
23 | remove_const :ENABLE_NIL_PARSER | |
24 | const_set :ENABLE_NIL_PARSER, true | |
25 | end | |
26 | java_import 'burp.IBurpExtender' | |
27 | java_import 'burp.ITab' | |
28 | java_import 'burp.IHttpListener' | |
29 | java_import 'burp.IProxyListener' | |
30 | java_import 'burp.IScannerListener' | |
31 | java_import 'burp.IExtensionStateListener' | |
32 | java_import 'burp.IExtensionHelpers' | |
33 | java_import 'burp.IContextMenuFactory' | |
34 | java_import 'java.net.InetAddress' | |
35 | java_import 'javax.swing.JMenuItem' | |
36 | java_import 'javax.swing.JCheckBox' | |
37 | java_import 'javax.swing.JPanel' | |
38 | java_import 'javax.swing.GroupLayout' | |
39 | java_import 'javax.swing.event.DocumentListener' | |
40 | ||
41 | class SimpleDocumentListener | |
42 | ||
43 | # This is how we declare that this class implements the Java | |
44 | # DocumentListener interface in JRuby: | |
45 | include DocumentListener | |
46 | ||
47 | attr_accessor :behavior | |
48 | ||
49 | def initialize(&behavior) | |
50 | self.behavior = behavior | |
51 | end | |
52 | ||
53 | def changedUpdate(event); behavior.call event; end | |
54 | def insertUpdate(event); behavior.call event; end | |
55 | def removeUpdate(event); behavior.call event; end | |
56 | ||
57 | end | |
58 | ||
59 | ||
60 | class BurpExtender | |
61 | include IBurpExtender, IHttpListener, IProxyListener, IScannerListener, IExtensionStateListener,IContextMenuFactory, ITab | |
62 | ||
63 | # | |
64 | # implement IBurpExtender | |
65 | # | |
66 | ||
67 | def registerExtenderCallbacks(callbacks) | |
68 | ||
69 | # keep a reference to our callbacks object | |
70 | @callbacks = callbacks | |
71 | ||
72 | # set our extension name | |
73 | callbacks.setExtensionName(PLUGINVERSION) | |
74 | ||
75 | # obtain our output stream | |
76 | @stdout = java.io.PrintWriter.new(callbacks.getStdout(), true) | |
77 | ||
78 | # create the tab | |
79 | ||
80 | @import_current_vulns = javax.swing.JButton.new("Import current vulnerabilities") | |
81 | @import_new_vulns = javax.swing.JCheckBox.new("Import new vulnerabilities") | |
82 | @rpc_server_label = javax.swing.JLabel.new("Faraday RPC server:") | |
83 | @rpc_server = javax.swing.JTextField.new() | |
84 | @rpc_server_label.setLabelFor(@rpc_server) | |
85 | @restore_btn = javax.swing.JButton.new("Restore configuration") | |
86 | @save_btn = javax.swing.JButton.new("Save configuration") | |
87 | ||
88 | @rpc_server.getDocument.addDocumentListener( | |
89 | SimpleDocumentListener.new do | |
90 | @server = XMLRPC::Client.new2(@rpc_server.getText()) | |
91 | end) | |
92 | ||
93 | @restore_btn.addActionListener do |e| | |
94 | restoreConfig() | |
95 | end | |
96 | @save_btn.addActionListener do |e| | |
97 | saveConfig() | |
98 | end | |
99 | @import_current_vulns.addActionListener do |e| | |
100 | importVulns() | |
101 | end | |
102 | ||
103 | restoreConfig() | |
104 | ||
105 | @tab = javax.swing.JPanel.new() | |
106 | ||
107 | @layout = javax.swing.GroupLayout.new(@tab) | |
108 | @tab.setLayout(@layout) | |
109 | @layout.setAutoCreateGaps(true) | |
110 | @layout.setAutoCreateContainerGaps(true) | |
111 | @layout.setHorizontalGroup( | |
112 | @layout.createParallelGroup() | |
113 | .addComponent(@import_current_vulns) | |
114 | .addComponent(@import_new_vulns) | |
115 | .addComponent(@rpc_server_label) | |
116 | .addComponent(@rpc_server) | |
117 | .addComponent(@restore_btn) | |
118 | .addComponent(@save_btn) | |
119 | ) | |
120 | @layout.setVerticalGroup( | |
121 | @layout.createSequentialGroup() | |
122 | .addComponent(@import_current_vulns) | |
123 | .addComponent(@import_new_vulns) | |
124 | .addComponent(@rpc_server_label) | |
125 | .addComponent(@rpc_server) | |
126 | .addComponent(@restore_btn) | |
127 | .addComponent(@save_btn) | |
128 | ) | |
129 | @layout.linkSize(javax.swing.SwingConstants.VERTICAL, @import_new_vulns, @rpc_server) | |
130 | ||
131 | callbacks.addSuiteTab(self) | |
132 | ||
133 | @helpers = callbacks.getHelpers() | |
134 | ||
135 | @stdout.println(PLUGINVERSION + " Loaded.") | |
136 | @stdout.println("RPCServer: " + @rpc_server.getText()) | |
137 | @stdout.println("Import new vulnerabilities detected: " + boolString(@callbacks.loadExtensionSetting("import_new_vulns"))) | |
138 | @stdout.println("------") | |
139 | ||
140 | # Register a factory for custom context menu items | |
141 | callbacks.registerContextMenuFactory(self) | |
142 | ||
143 | # register ourselves as a Scanner listener | |
144 | callbacks.registerScannerListener(self) | |
145 | ||
146 | # register ourselves as an extension state listener | |
147 | callbacks.registerExtensionStateListener(self) | |
148 | ||
149 | end | |
150 | ||
151 | ||
152 | # | |
153 | # implement menu | |
154 | # | |
155 | ||
156 | # Create a menu item if the appropriate section of the UI is selected | |
157 | def createMenuItems(invocation) | |
158 | ||
159 | menu = [] | |
160 | ||
161 | # Which part of the interface the user selects | |
162 | ctx = invocation.getInvocationContext() | |
163 | ||
164 | # Sitemap history, Proxy History, Request views, and Scanner will show menu item if selected by the user | |
165 | #@stdout.println('Menu TYPE: %s\n' % ctx) | |
166 | if ctx == 5 or ctx == 6 or ctx == 2 or ctx == 7 | |
167 | ||
168 | faradayMenu = JMenuItem.new("Send to Faraday", nil) | |
169 | ||
170 | faradayMenu.addActionListener do |e| | |
171 | eventScan(invocation, ctx) | |
172 | end | |
173 | ||
174 | menu.push(faradayMenu) | |
175 | end | |
176 | ||
177 | return menu | |
178 | end | |
179 | ||
180 | # | |
181 | ||
182 | # event click function | |
183 | # | |
184 | def eventScan(invocation, ctx) | |
185 | ||
186 | #Scanner click | |
187 | if ctx == 7 | |
188 | invMessage = invocation.getSelectedIssues() | |
189 | invMessage.each do |m| | |
190 | newScanIssue(m,ctx,true) | |
191 | end | |
192 | else | |
193 | #Others | |
194 | invMessage = invocation.getSelectedMessages() | |
195 | invMessage.each do |m| | |
196 | newScanIssue(m,ctx,true) | |
197 | end | |
198 | end | |
199 | end | |
200 | ||
201 | # | |
202 | # implement IScannerListener | |
203 | # | |
204 | def newScanIssue(issue, ctx=nil, import=nil) | |
205 | ||
206 | if import == nil && @import_new_vulns.isSelected() == false | |
207 | #ignore new issues | |
208 | return | |
209 | end | |
210 | ||
211 | host = issue.getHost() | |
212 | port = issue.getPort().to_s() | |
213 | url = issue.getUrl() | |
214 | resolution = "" | |
215 | ||
216 | begin | |
217 | ip = InetAddress.getByName(issue.getHttpService().getHost()).getHostAddress() | |
218 | rescue Exception => e | |
219 | ip = host | |
220 | end | |
221 | ||
222 | if ctx == 5 or ctx == 6 or ctx == 2 | |
223 | issuename="Analyzing: " | |
224 | severity="Information" | |
225 | desc="This request was manually sent using burp" | |
226 | else | |
227 | desc = issue.getIssueDetail().to_s.empty? ? "" : "Detail\n" + issue.getIssueDetail().to_s | |
228 | background = issue.getIssueBackground().to_s | |
229 | if !background.empty? | |
230 | desc.concat("Background\n").concat(background) | |
231 | end | |
232 | severity = issue.getSeverity().to_s | |
233 | issuename = issue.getIssueName().to_s | |
234 | resolution = issue.getRemediationBackground().to_s | |
235 | ||
236 | desc = desc.gsub(/<(\/p|br\/|\/li|ul|ol)>/, "\n").gsub(/<li>/, "* ").gsub(/<\/?[^>]*>/, "") | |
237 | resolution = resolution.gsub(/<(\/p|br\/|\/li)>/, "\n").gsub(/<li>/, "* ").gsub(/<\/?[^>]*>/, "") | |
238 | end | |
239 | ||
240 | @stdout.println("New scan issue host: " +host +",name:"+ issuename +",IP:" + ip) | |
241 | ||
242 | begin | |
243 | rt = @server.call("devlog", "[BURP] New issue generation") | |
244 | ||
245 | h_id = @server.call("createAndAddHost",ip, "unknown", [host]) | |
246 | i_id = @server.call("createAndAddInterface",h_id, ip,"00:00:00:00:00:00", ip, "0.0.0.0", "0.0.0.0",[], | |
247 | "0000:0000:0000:0000:0000:0000:0000:0000","00","0000:0000:0000:0000:0000:0000:0000:0000", | |
248 | [],"",host) | |
249 | ||
250 | s_id = @server.call("createAndAddServiceToInterface",h_id, i_id, issue.getProtocol(),"tcp",[port],"open") | |
251 | ||
252 | path = "" | |
253 | response = "" | |
254 | request = "" | |
255 | method = "" | |
256 | param = "" | |
257 | ||
258 | #Menu action | |
259 | if ctx == 5 or ctx == 6 or ctx == 2 | |
260 | req = @helpers.analyzeRequest(issue.getRequest()) | |
261 | ||
262 | param = getParam(req) | |
263 | issuename += "("+issue.getUrl().getPath()[0,20]+")" | |
264 | path = issue.getUrl().to_s | |
265 | request = issue.getRequest().to_s | |
266 | method = req.getMethod().to_s | |
267 | else #Scan event or Menu scan tab | |
268 | unless issue.getHttpMessages().nil? #issues with request #IHttpRequestResponse | |
269 | c = 0 | |
270 | issue.getHttpMessages().each do |m| | |
271 | if c == 0 | |
272 | req = @helpers.analyzeRequest(m.getRequest()) | |
273 | path = m.getUrl().to_s | |
274 | request = m.getRequest().to_s | |
275 | method = req.getMethod().to_s | |
276 | response = m.getResponse().to_s | |
277 | ||
278 | param = getParam(req) | |
279 | else | |
280 | desc += "Request (" + c.to_s + "): " + m.getUrl().to_s | |
281 | end | |
282 | ||
283 | c = c + 1 | |
284 | end | |
285 | ||
286 | if c == 0 | |
287 | path = issue.getUrl().to_s | |
288 | end | |
289 | end | |
290 | end | |
291 | ||
292 | #createAndAddVulnWebToService(host_id, service_id, name, desc, ref, severity, resolution, website, path, request, response,method,pname, params,query,category): | |
293 | v_id = @server.call("createAndAddVulnWebToService",h_id, s_id, issuename, | |
294 | desc,[],severity,resolution,host,path,request, | |
295 | response,method,"",param,"","") | |
296 | ||
297 | ||
298 | rescue XMLRPC::FaultException => e | |
299 | puts "Error:" | |
300 | puts e.faultCode | |
301 | puts e.faultString | |
302 | end | |
303 | end | |
304 | ||
305 | def extensionUnloaded() | |
306 | ||
307 | end | |
308 | ||
309 | def getTabCaption() | |
310 | return "Faraday" | |
311 | end | |
312 | ||
313 | def getUiComponent() | |
314 | return @tab | |
315 | end | |
316 | ||
317 | # | |
318 | # convert integer to string | |
319 | # | |
320 | def boolString(value) | |
321 | if value == "0" | |
322 | return "false" | |
323 | else | |
324 | return "true" | |
325 | end | |
326 | end | |
327 | ||
328 | # | |
329 | # get param for one url | |
330 | # | |
331 | def getParam(value) | |
332 | param = "" | |
333 | value.getParameters().each do |p| | |
334 | #TODO: Actually Get all parameters, cookies, jason, url, maybe we should get only url,get/post parameters | |
335 | #http://portswigger.net/burp/extender/api/constant-values.html#burp.IParameter.PARAM_BODY | |
336 | param += "%s" % p.getType() + ":" + p.getName() + "=" + p.getValue() + "," | |
337 | end | |
338 | return param | |
339 | ||
340 | end | |
341 | ||
342 | def restoreConfig(e=nil) | |
343 | @import_new_vulns.setSelected(@callbacks.loadExtensionSetting("import_new_vulns") == "1") | |
344 | @rpc_server.setText(@callbacks.loadExtensionSetting("rpc_server")) | |
345 | if @rpc_server.getText().strip == "" | |
346 | @rpc_server.setText("http://127.0.0.1:9876/") | |
347 | end | |
348 | ||
349 | #Connect Rpc server | |
350 | @server = XMLRPC::Client.new2(@rpc_server.getText()) | |
351 | ||
352 | @stdout.println("Config restored.") | |
353 | end | |
354 | ||
355 | def saveConfig(e=nil) | |
356 | @callbacks.saveExtensionSetting("import_new_vulns", @import_new_vulns.isSelected() ? "1" : "0") | |
357 | @callbacks.saveExtensionSetting("rpc_server", @rpc_server.getText()) | |
358 | @stdout.println("Config saved.") | |
359 | end | |
360 | ||
361 | ||
362 | def importVulns() | |
363 | # Get current vulnerabilities | |
364 | @stdout.println("Importing vulns.") | |
365 | rt = @server.call("devlog", "[BURP] Importing issues") | |
366 | @callbacks.getScanIssues(nil).each do |issue| | |
367 | newScanIssue(issue, 1, true) | |
368 | end | |
369 | end | |
370 | ||
371 | end |
39 | 39 | """ |
40 | 40 | |
41 | 41 | def __init__(self, output): |
42 | self.items = [] | |
43 | lists = output.split("\n\n") | |
42 | 44 | |
43 | self.items = [] | |
44 | lists = output.split("\n") | |
45 | for item in lists: | |
46 | sub_domain = item.split('\n') | |
47 | if len(sub_domain) == 2: | |
48 | ip = self.clean_ip(sub_domain[1]) | |
49 | host = sub_domain[0] | |
50 | self.parse_ip(ip, host) | |
51 | elif len(sub_domain) > 2: | |
52 | host = sub_domain.pop(0) | |
53 | for ip_address in sub_domain: | |
54 | ip = self.clean_ip(ip_address) | |
55 | self.parse_ip(ip, host) | |
45 | 56 | |
46 | for line in lists: | |
47 | mitem = line.split(',') | |
48 | if len(mitem) > 1: | |
49 | item = {'host': mitem[0], 'ip': mitem[1]} | |
50 | self.items.append(item) | |
57 | ||
58 | def clean_ip(self, item): | |
59 | ip = item.split(':', 1) | |
60 | return ip[1].strip() | |
61 | ||
62 | def parse_ip(self, ip_address, hostname): | |
63 | data = {} | |
64 | exists = False | |
65 | for item in self.items: | |
66 | if ip_address in item['ip']: | |
67 | item['hosts'].append(hostname) | |
68 | exists = True | |
69 | ||
70 | if not exists: | |
71 | data['ip'] = ip_address | |
72 | data['hosts'] = [hostname] | |
73 | self.items.append(data) | |
51 | 74 | |
52 | 75 | |
53 | 76 | class DnsmapPlugin(core.PluginBase): |
58 | 81 | core.PluginBase.__init__(self) |
59 | 82 | self.id = "Dnsmap" |
60 | 83 | self.name = "Dnsmap XML Output Plugin" |
61 | self.plugin_version = "0.0.2" | |
84 | self.plugin_version = "0.3" | |
62 | 85 | self.version = "0.30" |
63 | 86 | self.options = None |
64 | 87 | self._current_output = None |
66 | 89 | self._command_regex = re.compile( |
67 | 90 | r'^(sudo dnsmap|dnsmap|\.\/dnsmap).*?') |
68 | 91 | |
69 | self.xml_arg_re = re.compile(r"^.*(-c\s*[^\s]+).*$") | |
92 | self.xml_arg_re = re.compile(r"^.*(-r\s*[^\s]+).*$") | |
70 | 93 | |
71 | 94 | global current_path |
72 | 95 | |
73 | 96 | self._output_file_path = os.path.join( |
74 | 97 | self.data_path, |
75 | "%s_%s_output-%s.xml" % ( | |
98 | "%s_%s_output-%s.txt" % ( | |
76 | 99 | self.get_ws(), |
77 | 100 | self.id, |
78 | 101 | random.uniform(1, 10) |
90 | 113 | This method will discard the output the shell sends, it will read it |
91 | 114 | from the xml where it expects it to be present. |
92 | 115 | """ |
93 | ||
94 | 116 | parser = DnsmapParser(output) |
95 | ||
96 | 117 | for item in parser.items: |
97 | h_id = self.createAndAddHost(item['ip']) | |
98 | self.createAndAddInterface( | |
99 | h_id, | |
100 | item['ip'], | |
101 | ipv4_address=item['ip'], | |
102 | hostname_resolution=item['host']) | |
118 | h_id = self.createAndAddHost( | |
119 | item['ip'], | |
120 | hostnames=item['hosts']) | |
103 | 121 | |
104 | 122 | return True |
105 | 123 | |
111 | 129 | arg_match = self.xml_arg_re.match(command_string) |
112 | 130 | |
113 | 131 | if arg_match is None: |
114 | return "%s -c %s \n" % (command_string, self._output_file_path) | |
132 | return "%s -r %s \\n" % (command_string, self._output_file_path) | |
115 | 133 | else: |
116 | 134 | return re.sub(arg_match.group(1), |
117 | r"-c %s" % self._output_file_path, | |
135 | r"-r %s" % self._output_file_path, | |
118 | 136 | command_string) |
119 | 137 | |
120 | 138 |
14 | 14 | from collections import defaultdict |
15 | 15 | |
16 | 16 | from plugins import core |
17 | from plugins.plugins_utils import filter_services | |
17 | from plugins.plugins_utils import filter_services, get_all_protocols | |
18 | 18 | |
19 | 19 | |
20 | 20 | current_path = os.path.abspath(os.getcwd()) |
114 | 114 | add = False |
115 | 115 | #if "localhost" in combo: |
116 | 116 | if combo.count("|") > 1: |
117 | # Service with url, protocol and perhaps name | |
117 | 118 | items_service = combo.split('|') |
118 | if not items_service[0] in local_services and not items_service[0].startswith(':'): | |
119 | elements_ip_port = items_service[0].split(':') | |
120 | count = items_service[0].count(':') | |
121 | protocol = items_service[1] | |
122 | name = items_service[2] | |
119 | if not self.local_service(items_service, local_services): | |
120 | # self.aux_items will be an auxiliar list. We will use it... | |
121 | # ...for poping the url and the protocol so that the last element... | |
122 | # ... of the list, will be the name of the service | |
123 | self.aux_items = filter(None, items_service) | |
124 | elements_ip_port, count = self.get_ip_and_port(self.aux_items, remove_from_list=True) | |
125 | protocol = self.get_protocol() | |
126 | name = self.aux_items[0] | |
123 | 127 | add = True |
124 | 128 | |
125 | 129 | if name == '-': |
126 | 130 | details = self.search_service(elements_ip_port[1]) |
127 | 131 | name = details['name'] |
128 | 132 | elif combo.count('|') == 1: |
133 | # Service only with url | |
129 | 134 | items_service = combo.split('|') |
130 | if not items_service[0] in local_services and not items_service[0].startswith(':'): | |
131 | count = items_service[0].count(':') | |
132 | elements_ip_port = items_service[0].split(':') | |
135 | if not self.local_service(items_service, local_services): | |
136 | elements_ip_port, count = self.get_ip_and_port(items_service) | |
133 | 137 | details = self.search_service(elements_ip_port[1]) |
134 | 138 | protocol = details['protocol'] |
135 | 139 | name = details['name'] |
143 | 147 | name = details['name'] |
144 | 148 | add = True |
145 | 149 | |
146 | if add == True: | |
150 | if add: | |
147 | 151 | ip, port = self.colon_count(count, elements_ip_port, items_service) |
148 | 152 | elements_dict = { |
149 | 153 | "ip":ip, |
154 | 158 | return elements_dict |
155 | 159 | else: |
156 | 160 | return None |
161 | ||
162 | def local_service(self, service_data, local_services): | |
163 | ip = self.get_ip_and_port(service_data)[0][0] | |
164 | local = True | |
165 | if not ip in local_services and not ip.startswith(':'): | |
166 | local = False | |
167 | ||
168 | return local | |
169 | ||
170 | def get_ip_and_port(self, service_data, remove_from_list=False): | |
171 | url_data = [url for url in service_data if ':' in url][0] | |
172 | count = url_data.count(':') | |
173 | ip_port = url_data.split(':') | |
174 | ||
175 | if remove_from_list: | |
176 | self.aux_items.remove(url_data) | |
177 | ||
178 | return ip_port, count | |
179 | ||
180 | def get_protocol(self): | |
181 | # network_listen_port variables are different in .log and .dat reports | |
182 | # .log: tcp4|127.0.0.1:5985|zabbix_age| | |
183 | # .dat: 127.0.0.1:5985|tcp4|zabbix_age| | |
184 | # This method will check if the protocol (from the function get_all_protocols()) | |
185 | # matches with the protocol that network_listen_port contains | |
186 | protocols = get_all_protocols() | |
187 | for item in protocols: | |
188 | protocol = [p for p in self.aux_items if item in p.lower()] | |
189 | if protocol: | |
190 | self.aux_items.remove(protocol[0]) | |
191 | return protocol[0] | |
157 | 192 | |
158 | 193 | def search_service(self, port): |
159 | 194 | srv = filter_services() |
113 | 113 | if t.get('host-ip'): |
114 | 114 | ip = t.get('host-ip') |
115 | 115 | |
116 | if not ip: | |
117 | if not t.get_ips(): | |
118 | continue | |
119 | ip = t.get_ips().pop() | |
120 | ||
116 | 121 | h_id = self.createAndAddHost(ip, t.get('operating-system'), hostnames=[host]) |
117 | 122 | |
118 | 123 | if self._isIPV4(ip): |
119 | 124 | i_id = self.createAndAddInterface( |
120 | h_id, ip, mac, ipv4_address=ip, hostname_resolution=host) | |
125 | h_id, ip, mac, ipv4_address=ip, hostname_resolution=[host]) | |
121 | 126 | else: |
122 | 127 | i_id = self.createAndAddInterface( |
123 | h_id, ip, mac, ipv6_address=ip, hostname_resolution=host) | |
128 | h_id, ip, mac, ipv6_address=ip, hostname_resolution=[host]) | |
124 | 129 | |
125 | 130 | srv = {} |
126 | 131 | web = False |
14 | 14 | import sys |
15 | 15 | import socket |
16 | 16 | import urllib |
17 | from bs4 import BeautifulSoup | |
17 | 18 | |
18 | 19 | try: |
19 | 20 | import xml.etree.cElementTree as ET |
112 | 113 | self.port = host.group(11) |
113 | 114 | |
114 | 115 | self.name = self.get_text_from_subnode("type") |
116 | self.desc = self.get_text_from_subnode("description") | |
115 | 117 | self.severity = self.re_map_severity(self.get_text_from_subnode("severity")) |
116 | 118 | self.certainty = self.get_text_from_subnode("certainty") |
117 | 119 | self.method = self.get_text_from_subnode("vulnerableparametertype") |
118 | 120 | self.param = self.get_text_from_subnode("vulnerableparameter") |
119 | 121 | self.paramval = self.get_text_from_subnode("vulnerableparametervalue") |
122 | self.reference = self.get_text_from_subnode("externalReferences") | |
123 | self.resolution = self.get_text_from_subnode("actionsToTake") | |
120 | 124 | self.request = self.get_text_from_subnode("rawrequest") |
121 | 125 | self.response = self.get_text_from_subnode("rawresponse") |
122 | 126 | if self.response: |
123 | 127 | self.response = self.response.encode("ascii",errors="backslashreplace") |
124 | 128 | if self.request: |
125 | 129 | self.request = self.request.encode("ascii",errors="backslashreplace") |
130 | if self.reference: | |
131 | self.reference = self.reference.encode("ascii",errors="backslashreplace") | |
132 | ||
126 | 133 | |
127 | 134 | self.kvulns = [] |
128 | 135 | for v in self.node.findall("knownvulnerabilities/knownvulnerability"): |
142 | 149 | self.capec = self.get_text_from_subnode("CAPEC") |
143 | 150 | self.pci = self.get_text_from_subnode("PCI") |
144 | 151 | self.pci2 = self.get_text_from_subnode("PCI2") |
152 | self.node = item_node.find("classification/CVSS") | |
153 | self.cvss = self.get_text_from_subnode("vector") | |
145 | 154 | |
146 | 155 | self.ref = [] |
147 | 156 | if self.cwe: |
148 | 157 | self.ref.append("CWE-" + self.cwe) |
149 | 158 | if self.owasp: |
150 | 159 | self.ref.append("OWASP-" + self.owasp) |
151 | ||
152 | self.desc = "" | |
153 | self.desc += "\nKnowVulns: " + \ | |
160 | if self.reference: | |
161 | self.ref.extend(list(set(re.findall('https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+', self.reference)))) | |
162 | if self.cvss: | |
163 | self.ref.append(self.cvss) | |
164 | ||
165 | self.data = "" | |
166 | self.data += "\nKnowVulns: " + \ | |
154 | 167 | "\n".join(self.kvulns) if self.kvulns else "" |
155 | self.desc += "\nWASC: " + self.wasc if self.wasc else "" | |
156 | self.desc += "\nPCI: " + self.pci if self.pci else "" | |
157 | self.desc += "\nPCI2: " + self.pci2 if self.pci2 else "" | |
158 | self.desc += "\nCAPEC: " + self.capec if self.capec else "" | |
159 | self.desc += "\nPARAM: " + self.param if self.param else "" | |
160 | self.desc += "\nPARAM VAL: " + \ | |
168 | self.data += "\nWASC: " + self.wasc if self.wasc else "" | |
169 | self.data += "\nCertainty: " + self.certainty if self.certainty else "" | |
170 | self.data += "\nPCI: " + self.pci if self.pci else "" | |
171 | self.data += "\nPCI2: " + self.pci2 if self.pci2 else "" | |
172 | self.data += "\nCAPEC: " + self.capec if self.capec else "" | |
173 | self.data += "\nPARAM: " + self.param if self.param else "" | |
174 | self.data += "\nPARAM VAL: " + \ | |
161 | 175 | repr(self.paramval) if self.paramval else "" |
162 | self.desc += "\nExtra: " + "\n".join(self.extra) if self.extra else "" | |
176 | self.data += "\nExtra: " + "\n".join(self.extra) if self.extra else "" | |
163 | 177 | |
164 | 178 | def get_text_from_subnode(self, subnode_xpath_expr): |
165 | 179 | """ |
210 | 224 | for i in parser.items: |
211 | 225 | if first: |
212 | 226 | ip = self.resolve(i.hostname) |
213 | h_id = self.createAndAddHost(ip) | |
214 | i_id = self.createAndAddInterface( | |
215 | h_id, ip, ipv4_address=ip, hostname_resolution=i.hostname) | |
216 | ||
217 | s_id = self.createAndAddServiceToInterface(h_id, i_id, str(i.port), | |
218 | str(i.protocol), | |
227 | h_id = self.createAndAddHost(ip, hostnames=[ip]) | |
228 | ||
229 | s_id = self.createAndAddServiceToHost(h_id, str(i.port), | |
230 | protocol = str(i.protocol), | |
219 | 231 | ports=[str(i.port)], |
220 | 232 | status="open") |
221 | ||
222 | n_id = self.createAndAddNoteToService( | |
223 | h_id, s_id, "website", "") | |
224 | n2_id = self.createAndAddNoteToNote( | |
225 | h_id, s_id, n_id, i.hostname, "") | |
226 | 233 | 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=i.desc, path=i.url, method=i.method, | |
230 | request=i.request, response=i.response, pname=i.param) | |
234 | ||
235 | v_id = self.createAndAddVulnWebToService(h_id, s_id, i.name, ref=i.ref, website=i.hostname, | |
236 | severity=i.severity, desc=BeautifulSoup(i.desc, "lxml").text, | |
237 | path=i.url, method=i.method, request=i.request, response=i.response, | |
238 | resolution=BeautifulSoup(i.resolution, "lxml").text,pname=i.param, data=i.data) | |
231 | 239 | |
232 | 240 | del parser |
233 | 241 | |
234 | 242 | def processCommandString(self, username, current_path, command_string): |
235 | 243 | return None |
236 | ||
237 | def setHost(self): | |
238 | pass | |
239 | ||
240 | 244 | |
241 | 245 | def createPlugin(): |
242 | 246 | return NetsparkerPlugin() |
16 | 16 | import socket |
17 | 17 | import sqlite3 |
18 | 18 | import sys |
19 | from urlparse import urlparse | |
19 | 20 | from BaseHTTPServer import BaseHTTPRequestHandler |
20 | 21 | from StringIO import StringIO |
21 | 22 | from urlparse import urlparse |
46 | 47 | __status__ = "Development" |
47 | 48 | |
48 | 49 | |
50 | # This is the value of the HASHDB_MILESTONE_VALUE constant | |
51 | # in the lib/core/settings.py file of sqlmap. | |
52 | # If that value is changed in a newer version of SQLMap, it means that the | |
53 | # hashdb mechanism has backwards-incompatible changes that probably will | |
54 | # break our plugin, so the plugin will show an error and abort | |
55 | SUPPORTED_HASHDB_VERSIONS = { | |
56 | "dPHoJRQYvs", # 1.0.11 | |
57 | "BZzRotigLX", # 1.2.8 | |
58 | } | |
59 | ||
60 | ||
49 | 61 | class Database(object): |
50 | 62 | |
51 | 63 | def __init__(self, database): |
83 | 95 | self.id = "Sqlmap" |
84 | 96 | self.name = "Sqlmap" |
85 | 97 | self.plugin_version = "0.0.3" |
86 | self.version = "1.0.8.15#dev" | |
98 | self.version = "1.2.8" | |
87 | 99 | self.framework_version = "1.0.0" |
88 | 100 | self._current_output = None |
89 | 101 | self.url = "" |
141 | 153 | Helper function for restoring session data from HashDB |
142 | 154 | """ |
143 | 155 | |
144 | key = "%s%s%s" % (self.url or "%s%s" % ( | |
145 | self.hostname, self.port), key, self.HASHDB_MILESTONE_VALUE) | |
156 | if self.HASHDB_MILESTONE_VALUE == 'dPHoJRQYvs': | |
157 | # Support for old SQLMap versions | |
158 | key = "%s%s%s" % (self.url or "%s%s" % ( | |
159 | self.hostname, self.port), key, self.HASHDB_MILESTONE_VALUE) | |
160 | else: | |
161 | url = urlparse(self.url) | |
162 | key = '|'.join([ | |
163 | url.hostname, | |
164 | url.path.strip('/'), | |
165 | key, | |
166 | self.HASHDB_MILESTONE_VALUE | |
167 | ]) | |
168 | ||
146 | 169 | retVal = '' |
147 | ||
148 | 170 | hash_ = self.hashKey(key) |
149 | 171 | |
150 | 172 | if not retVal: |
395 | 417 | self.log('Remember set your Sqlmap Path Setting!... Abort plugin.', 'ERROR') |
396 | 418 | return |
397 | 419 | |
420 | if HASHDB_MILESTONE_VALUE not in SUPPORTED_HASHDB_VERSIONS: | |
421 | self.log( | |
422 | "Your version of SQLMap is not supported with this plugin. " | |
423 | "Please use an older version of SQLMap (the suggested one " | |
424 | "is \"{}\"). Also, we suggest you to open issue in our GitHub " | |
425 | "issue tracker: https://github.com/infobyte/faraday/issues/".format(self.version), | |
426 | 'ERROR') | |
427 | return | |
428 | ||
398 | 429 | self.HASHDB_MILESTONE_VALUE = HASHDB_MILESTONE_VALUE |
399 | 430 | self.HASHDB_KEYS = HASHDB_KEYS |
400 | 431 | self.UNICODE_ENCODING = UNICODE_ENCODING |
459 | 490 | self.hostname, |
460 | 491 | '') |
461 | 492 | |
462 | db_port = 80 | |
493 | db_port = 0 | |
463 | 494 | for item in self.db_port.keys(): |
464 | 495 | if dbms_version.find(item) >= 0: |
465 | 496 | db_port = self.db_port[item] |
92 | 92 | self.id = "Sslyze" |
93 | 93 | self.name = "Sslyze Plugin" |
94 | 94 | self.plugin_version = "0.0.1" |
95 | self.version = "1.4.2" | |
95 | self.version = "2.0.6" | |
96 | 96 | self.framework_version = "1.0.0" |
97 | 97 | self.options = None |
98 | 98 | self._current_output = None |
Binary diff not shown
7 | 7 | Flask-Security>=3.0.0 |
8 | 8 | flask-session>=0.3.1 |
9 | 9 | flask>=1.0 |
10 | future>=0.17.1 | |
10 | 11 | IPy>=0.83 |
11 | 12 | marshmallow>=2.15.3 |
12 | 13 | Pillow>=4.2.1 |
21 | 22 | sqlalchemy_schemadisplay>=1.3 |
22 | 23 | tqdm>=4.15.0 |
23 | 24 | twisted>=18.7.0 |
24 | #webargs==5.0.0 fails with Python 2 | |
25 | webargs==4.4.1 | |
25 | webargs>=5.1.0 | |
26 | 26 | marshmallow-sqlalchemy |
27 | 27 | git+https://github.com/infobyte/filteralchemy@dev#egg=filteralchemy |
28 | 28 | filedepot>=0.5.0 |
32 | 32 | websocket-client>=0.46.0 |
33 | 33 | attrs>=17.4.0 |
34 | 34 | Flask-Restless==0.17.0 |
35 | simplejson>=3.16.0 |
24 | 24 | 'object': "regex=^MS17-010", |
25 | 25 | 'conditions': ["creator=Nessus"], |
26 | 26 | 'actions': ["--UPDATE:refs=https://docs.microsoft.com/en-us/security-updates/securitybulletins/2017/ms17-010"] |
27 | }, | |
28 | ||
29 | { | |
30 | 'id': 'APPLY_MS_REFS_{{id}}', | |
31 | 'model': 'Vulnerability', | |
32 | 'object': "regex=^{{tag}}", | |
33 | 'conditions': ["creator=Nessus"], | |
34 | 'actions': ["--UPDATE:refs=https://docs.microsoft.com/en-us/security-updates/securitybulletins/{{year}}/{{tag}}"], | |
35 | 'values': [{'tag': 'ms17-010', 'year': '2017', 'id': 'DYNAMIC'}, {'tag': 'ms18-010', 'year': '2018', 'id': 'DYNAMIC'}] | |
36 | }, | |
37 | ||
38 | { | |
39 | 'id': 'APPLY_REFS_ACCORDING_SEVERITY_{{sev}}', | |
40 | 'model': 'Vulnerability', | |
41 | 'object': "severity={{sev}}", | |
42 | 'actions': ["--UPDATE:refs=https://docs.microsoft.com/en-us/security-updates/securitybulletins/{{year}}/{{tag}}"], | |
43 | 'values': [{'tag': 'ms17-010', 'year': '2017', 'sev': 'low'}, {'tag': 'ms18-010', 'year': '2018', 'sev': 'med'}] | |
27 | 44 | } |
28 | 45 | ] |
19 | 19 | from email.mime.text import MIMEText |
20 | 20 | import requests |
21 | 21 | import json |
22 | import ast | |
22 | 23 | from config.configuration import getInstanceConfiguration |
23 | 24 | from persistence.server import models |
24 | 25 | from persistence.server import server |
36 | 37 | CONF = getInstanceConfiguration() |
37 | 38 | |
38 | 39 | |
40 | mail_from = '' | |
41 | mail_password = '' | |
42 | mail_protocol = 'smtp.gmail.com' | |
43 | mail_port = 587 | |
44 | ||
45 | ||
39 | 46 | def send_mail(to_addr, subject, body): |
40 | from_addr = '[email protected]' | |
47 | global mail_from, mail_password, mail_protocol, mail_port | |
48 | from_addr = mail_from | |
41 | 49 | msg = MIMEMultipart() |
42 | 50 | msg['From'] = from_addr |
43 | 51 | msg['To'] = to_addr |
45 | 53 | |
46 | 54 | msg.attach(MIMEText(body, 'plain')) |
47 | 55 | try: |
48 | server_mail = smtplib.SMTP('smtp.gmail.com', 587) | |
56 | server_mail = smtplib.SMTP(mail_protocol, mail_port) | |
49 | 57 | server_mail.starttls() |
50 | server_mail.login(from_addr, "faradaySearcher.2018") | |
58 | server_mail.login(from_addr, mail_password) | |
51 | 59 | text = msg.as_string() |
52 | 60 | server_mail.sendmail(from_addr, to_addr, text) |
53 | 61 | server_mail.quit() |
395 | 403 | return False |
396 | 404 | |
397 | 405 | if isinstance(temp_value, list): |
398 | if value not in temp_value and str(value) not in temp_value and int(value) not in temp_value: | |
399 | return False | |
406 | if value not in temp_value and str(value) not in temp_value: | |
407 | if not isinstance(value, int): | |
408 | return False | |
409 | elif int(value) not in temp_value: | |
410 | return False | |
400 | 411 | return True |
401 | 412 | |
402 | 413 | if isinstance(temp_value, bool): |
522 | 533 | return True |
523 | 534 | |
524 | 535 | |
536 | def replace_rule(rule, value_item): | |
537 | if value_item is None: | |
538 | return rule | |
539 | ||
540 | rule_str = json.dumps(rule) | |
541 | r = re.findall("\{\{(.*?)\}\}", rule_str) | |
542 | _vars = list(set(r)) | |
543 | for var in _vars: | |
544 | value = value_item[var] | |
545 | rule_str = rule_str.replace('{{'+var+'}}', value) | |
546 | ||
547 | return ast.literal_eval(rule_str) | |
548 | ||
549 | ||
525 | 550 | def process_vulnerabilities(ws, vulns, _server): |
526 | 551 | logger.debug("--> Start Process vulnerabilities") |
527 | for rule in rules: | |
528 | if rule['model'] == 'Vulnerability': | |
529 | vulnerabilities = get_models(ws, vulns, rule) | |
530 | if 'fields' in rule: | |
531 | process_models_by_similarity(ws, vulnerabilities, rule, _server) | |
532 | else: | |
533 | _objs_value = None | |
534 | if 'object' in rule: | |
535 | _objs_value = rule['object'] | |
536 | objects = get_object(vulnerabilities, _objs_value) | |
537 | if objects is not None and len(objects) != 0: | |
538 | if 'conditions' in rule: | |
539 | if can_execute_action(vulnerabilities, rule['conditions']): | |
552 | for rule_item in rules: | |
553 | if rule_item['model'] == 'Vulnerability': | |
554 | count_values = 1 | |
555 | values = [None] | |
556 | if 'values' in rule_item and len(rule_item['values']) > 0: | |
557 | values = rule_item['values'] | |
558 | count_values = len(values) | |
559 | ||
560 | for index in range(count_values): | |
561 | rule = replace_rule(rule_item, values[index]) | |
562 | vulnerabilities = get_models(ws, vulns, rule) | |
563 | if 'fields' in rule: | |
564 | process_models_by_similarity(ws, vulnerabilities, rule, _server) | |
565 | else: | |
566 | _objs_value = None | |
567 | if 'object' in rule: | |
568 | _objs_value = rule['object'] | |
569 | objects = get_object(vulnerabilities, _objs_value) | |
570 | if objects is not None and len(objects) != 0: | |
571 | if 'conditions' in rule: | |
572 | if can_execute_action(vulnerabilities, rule['conditions']): | |
573 | execute_action(ws, objects, rule, _server) | |
574 | else: | |
540 | 575 | execute_action(ws, objects, rule, _server) |
541 | else: | |
542 | execute_action(ws, objects, rule, _server) | |
543 | 576 | logger.debug("<-- Finish Process vulnerabilities") |
544 | 577 | |
545 | 578 | |
611 | 644 | parser.add_argument('-u', '--user', help='Faraday user', required=False, default="") |
612 | 645 | parser.add_argument('-p', '--password', help='Faraday password', required=False, default="") |
613 | 646 | parser.add_argument('-o', '--output', help='Choose a custom output directory', required=False) |
647 | parser.add_argument('-e', '--email', help='Custom email', required=False, default="[email protected]") | |
648 | parser.add_argument('-ep', '--email_pass', help='Email password', required=False) | |
649 | parser.add_argument('-mp', '--mail_protocol', help='Email protocol', required=False, default="smtp.gmail.com") | |
650 | parser.add_argument('-pp', '--port_protocol', help='Port protocol', required=False, default=587) | |
614 | 651 | parser.add_argument('-l', '--log', help='Choose a custom log level', required=False) |
615 | 652 | args = parser.parse_args() |
616 | 653 | |
646 | 683 | loglevel = 'debug' |
647 | 684 | if args.log: |
648 | 685 | loglevel = args.log |
686 | ||
687 | global mail_from, mail_password, mail_protocol, mail_port | |
688 | ||
689 | if args.email: | |
690 | mail_from = args.email | |
691 | ||
692 | if args.email_pass: | |
693 | mail_password = args.email_pass | |
694 | ||
695 | if args.mail_protocol: | |
696 | mail_protocol = args.mail_protocol | |
697 | ||
698 | if args.port_protocol: | |
699 | mail_port = args.port_protocol | |
649 | 700 | |
650 | 701 | for d in [output, 'log/']: |
651 | 702 | if not os.path.isdir(d): |
712 | 763 | os.remove(lockf) |
713 | 764 | exit(0) |
714 | 765 | |
715 | # except Exception as errorMsg: | |
716 | # logger.error(errorMsg) | |
717 | # os.remove(lockf) | |
718 | # exit(0) | |
766 | except Exception as errorMsg: | |
767 | logger.error(errorMsg) | |
768 | os.remove(lockf) | |
769 | exit(0) | |
719 | 770 | |
720 | 771 | |
721 | 772 | if __name__ == "__main__": |
7 | 7 | ### |
8 | 8 | |
9 | 9 | import re |
10 | import json | |
10 | 11 | import logging |
11 | 12 | from rules import * |
12 | 13 | |
82 | 83 | return True |
83 | 84 | |
84 | 85 | |
86 | def validate_values(values, rule, rule_id): | |
87 | r = re.findall("\{\{(.*?)\}\}", json.dumps(rule)) | |
88 | _vars = list(set(r)) | |
89 | keys = [] | |
90 | for index, item in enumerate(values): | |
91 | if index != 0: | |
92 | if len(values[index - 1]) != len(values[index]): | |
93 | logger.error("Each value item must be equal in rule: %s" % rule_id) | |
94 | return False | |
95 | keys = item.keys() | |
96 | ||
97 | for var in _vars: | |
98 | if var not in keys: | |
99 | logger.error("Variable '%s' should has a value in rule: %s" % (var, rule_id)) | |
100 | return False | |
101 | return True | |
102 | ||
103 | ||
85 | 104 | def validate_action(actions): |
86 | 105 | if len(actions) == 0: |
87 | 106 | return False |
133 | 152 | return False |
134 | 153 | return True |
135 | 154 | |
155 | if key == 'values': | |
156 | return validate_function(dictionary[key], dictionary, rule_id) | |
157 | ||
136 | 158 | if not validate_function(dictionary[key]): |
137 | 159 | logger.error("ERROR: Key %s has an invalid value in rule: %s" % (key, rule_id)) |
138 | 160 | return False |
168 | 190 | if not validate('actions', rule, validate_action, rule_id): |
169 | 191 | return False |
170 | 192 | |
193 | if not validate('values', rule, validate_values, rule_id, mandatory=False): | |
194 | return False | |
195 | ||
171 | 196 | logger.info('<-- Rules OK') |
172 | 197 | return True |
345 | 345 | route_prefix = '/v2/ws/<workspace_name>/' |
346 | 346 | base_args = ['workspace_name'] # Required to prevent double usage of <workspace_name> |
347 | 347 | |
348 | def _get_workspace(self, workspace_name, to_edit=True): | |
348 | def _get_workspace(self, workspace_name): | |
349 | 349 | try: |
350 | 350 | ws = Workspace.query.filter_by(name=workspace_name).one() |
351 | if to_edit and not ws.active: | |
351 | if not ws.active: | |
352 | 352 | flask.abort(403, "Disabled workspace: %s" % workspace_name) |
353 | 353 | except NoResultFound: |
354 | 354 | flask.abort(404, "No such workspace: %s" % workspace_name) |
357 | 357 | def _get_base_query(self, workspace_name): |
358 | 358 | base = super(GenericWorkspacedView, self)._get_base_query() |
359 | 359 | return base.join(Workspace).filter( |
360 | Workspace.id == self._get_workspace(workspace_name, to_edit=False).id) | |
360 | Workspace.id == self._get_workspace(workspace_name).id) | |
361 | 361 | |
362 | 362 | def _get_object(self, object_id, workspace_name, eagerload=False): |
363 | 363 | self._validate_object_id(object_id) |
375 | 375 | """Overriden to pass the workspace name to the schema""" |
376 | 376 | context.update(kwargs) |
377 | 377 | return context |
378 | ||
379 | def before_request(self, name, *args, **kwargs): | |
380 | sup = super(GenericWorkspacedView, self) | |
381 | if hasattr(sup, 'before_request'): | |
382 | sup.before_request(name, *args, **kwargs) | |
383 | if (self._get_workspace(kwargs['workspace_name']).readonly and | |
384 | flask.request.method not in ['GET', 'HEAD', 'OPTIONS']): | |
385 | flask.abort(403, "Altering a readonly workspace is not allowed") | |
378 | 386 | |
379 | 387 | |
380 | 388 | class ListMixin(object): |
42 | 42 | credentials = fields.Integer(attribute='credentials_count', dump_only=True) |
43 | 43 | metadata = SelfNestedField(MetadataSchema()) |
44 | 44 | type = fields.Function(lambda obj: 'Service', dump_only=True) |
45 | summary = fields.String(dump_only=True) | |
45 | 46 | |
46 | 47 | def load_ports(self, value): |
47 | 48 | if not isinstance(value, list): |
8 | 8 | FilterSet, |
9 | 9 | operators, |
10 | 10 | ) |
11 | from marshmallow import fields, ValidationError | |
11 | from marshmallow import fields, ValidationError, Schema, post_load | |
12 | 12 | from marshmallow.validate import OneOf |
13 | 13 | |
14 | 14 | import server.utils.logger |
20 | 20 | PaginatedMixin, |
21 | 21 | ReadWriteView, |
22 | 22 | ) |
23 | from server.models import VulnerabilityTemplate | |
24 | from server.schemas import PrimaryKeyRelatedField, SeverityField | |
23 | from server.models import VulnerabilityTemplate, Vulnerability | |
24 | from server.schemas import PrimaryKeyRelatedField, SeverityField, SelfNestedField | |
25 | 25 | |
26 | 26 | vulnerability_template_api = Blueprint('vulnerability_template_api', __name__) |
27 | 27 | logger = server.utils.logger.get_logger(__name__) |
28 | ||
29 | ||
30 | class ImpactSchema(Schema): | |
31 | accountability = fields.Boolean(attribute='impact_accountability', default=False) | |
32 | availability = fields.Boolean(attribute='impact_availability', default=False) | |
33 | confidentiality = fields.Boolean(attribute='impact_confidentiality', default=False) | |
34 | integrity = fields.Boolean(attribute='impact_integrity', default=False) | |
28 | 35 | |
29 | 36 | |
30 | 37 | class VulnerabilityTemplateSchema(AutoSchema): |
36 | 43 | references = fields.Method('get_references', deserialize='load_references', required=True) |
37 | 44 | refs = fields.List(fields.String(), dump_only=True, attribute='references') |
38 | 45 | desc = fields.String(dump_only=True, attribute='description') |
46 | data = fields.String(attribute='data') | |
47 | impact = SelfNestedField(ImpactSchema()) | |
48 | easeofresolution = fields.String( | |
49 | attribute='ease_of_resolution', | |
50 | validate=OneOf(Vulnerability.EASE_OF_RESOLUTIONS), | |
51 | allow_none=True) | |
52 | policyviolations = fields.List(fields.String, | |
53 | attribute='policy_violations') | |
39 | 54 | |
40 | 55 | class Meta: |
41 | 56 | model = VulnerabilityTemplate |
42 | 57 | fields = ('id', '_id', '_rev', 'cwe', 'description', 'desc', |
43 | 'exploitation', 'name', 'references', 'refs', 'resolution') | |
58 | 'exploitation', 'name', 'references', 'refs', 'resolution', | |
59 | 'impact', 'easeofresolution', 'policyviolations', 'data') | |
44 | 60 | |
45 | 61 | def get_references(self, obj): |
46 | 62 | return ', '.join(map(lambda ref_tmpl: ref_tmpl.name, obj.reference_template_instances)) |
59 | 75 | if any(len(ref) == 0 for ref in references): |
60 | 76 | raise ValidationError('Empty name detected in reference') |
61 | 77 | return references |
78 | ||
79 | @post_load | |
80 | def post_load_impact(self, data): | |
81 | # Unflatten impact (move data[impact][*] to data[*]) | |
82 | impact = data.pop('impact', None) | |
83 | if impact: | |
84 | data.update(impact) | |
85 | return data | |
62 | 86 | |
63 | 87 | |
64 | 88 | class VulnerabilityTemplateFilterSet(FilterSet): |
7 | 7 | from base64 import b64encode, b64decode |
8 | 8 | |
9 | 9 | import flask |
10 | import wtforms | |
10 | 11 | from filteralchemy import Filter, FilterSet, operators |
11 | 12 | from flask import request |
12 | 13 | from flask import Blueprint |
13 | 14 | from flask_classful import route |
14 | 15 | from flask_restless.search import search |
16 | from flask_wtf.csrf import validate_csrf | |
15 | 17 | from marshmallow import Schema, fields, post_load, ValidationError |
16 | 18 | from marshmallow.validate import OneOf |
17 | 19 | from sqlalchemy.orm import aliased, joinedload, selectin_polymorphic, undefer |
104 | 106 | policyviolations = fields.List(fields.String, |
105 | 107 | attribute='policy_violations') |
106 | 108 | refs = fields.List(fields.String(), attribute='references') |
107 | issuetracker = fields.Method(serialize='get_issuetracker') | |
109 | issuetracker = fields.Method(serialize='get_issuetracker', dump_only=True) | |
108 | 110 | parent = fields.Method(serialize='get_parent', deserialize='load_parent', required=True) |
109 | 111 | parent_type = MutableField(fields.Method('get_parent_type'), |
110 | 112 | fields.String(), |
278 | 280 | return query.filter(model.id == value) |
279 | 281 | |
280 | 282 | |
283 | class StatusCodeFilter(Filter): | |
284 | def filter(self, query, model, attr, value): | |
285 | return query.filter(model.status_code == value) | |
286 | ||
287 | ||
281 | 288 | class TargetFilter(Filter): |
282 | 289 | def filter(self, query, model, attr, value): |
283 | 290 | return query.filter(model.target_host_ip.ilike("%" + value + "%")) |
377 | 384 | allow_none=True)) |
378 | 385 | pname = Filter(fields.String(attribute='parameter_name')) |
379 | 386 | query = Filter(fields.String(attribute='query_string')) |
387 | status_code = StatusCodeFilter(fields.Int()) | |
380 | 388 | params = Filter(fields.String(attribute='parameters')) |
381 | 389 | status = Filter(fields.Function( |
382 | 390 | deserialize=lambda val: 'open' if val == 'opened' else val, |
570 | 578 | res['groups'] = [convert_group(group) for group in res['groups']] |
571 | 579 | return res |
572 | 580 | |
573 | @route('/<vuln_id>/attachment/', methods=['POST']) | |
581 | @route('/<int:vuln_id>/attachment/', methods=['POST']) | |
574 | 582 | def post_attachment(self, workspace_name, vuln_id): |
583 | try: | |
584 | validate_csrf(request.form.get('csrf_token')) | |
585 | except wtforms.ValidationError: | |
586 | flask.abort(403) | |
575 | 587 | vuln_workspace_check = db.session.query(VulnerabilityGeneric, Workspace.id).join( |
576 | 588 | Workspace).filter(VulnerabilityGeneric.id == vuln_id, |
577 | 589 | Workspace.name == workspace_name).first() |
626 | 638 | web_vulns_data = [] |
627 | 639 | return self._envelope_list(normal_vulns_data + web_vulns_data) |
628 | 640 | |
629 | @route('/<vuln_id>/attachment/<attachment_filename>/', methods=['GET']) | |
641 | @route('/<int:vuln_id>/attachment/<attachment_filename>/', methods=['GET']) | |
630 | 642 | def get_attachment(self, workspace_name, vuln_id, attachment_filename): |
631 | 643 | vuln_workspace_check = db.session.query(VulnerabilityGeneric, Workspace.id).join( |
632 | 644 | Workspace).filter(VulnerabilityGeneric.id == vuln_id, |
655 | 667 | else: |
656 | 668 | flask.abort(404, "Vulnerability not found") |
657 | 669 | |
658 | @route('/<vuln_id>/attachment/<attachment_filename>/', methods=['DELETE']) | |
670 | @route('/<int:vuln_id>/attachment/<attachment_filename>/', methods=['DELETE']) | |
659 | 671 | def delete_attachment(self, workspace_name, vuln_id, attachment_filename): |
660 | 672 | vuln_workspace_check = db.session.query(VulnerabilityGeneric, Workspace.id).join( |
661 | 673 | Workspace).filter( |
58 | 58 | PrimaryKeyRelatedField('name', many=True, dump_only=True), |
59 | 59 | fields.List(fields.String) |
60 | 60 | ) |
61 | active = fields.Boolean(dump_only=True) | |
61 | 62 | |
62 | 63 | create_date = fields.DateTime(attribute='create_date', |
63 | 64 | dump_only=True) |
70 | 71 | model = Workspace |
71 | 72 | fields = ('_id', 'id', 'customer', 'description', 'active', |
72 | 73 | 'duration', 'name', 'public', 'scope', 'stats', |
73 | 'create_date', 'update_date') | |
74 | 'create_date', 'update_date', 'readonly') | |
74 | 75 | |
75 | 76 | @post_load |
76 | 77 | def post_load_duration(self, data): |
106 | 107 | objects.append(workspace_stat) |
107 | 108 | return self._envelope_list(self._dump(objects, kwargs, many=True)) |
108 | 109 | |
110 | def _get_querystring_boolean_field(self, field_name, default=None): | |
111 | try: | |
112 | val = bool(json.loads(flask.request.args[field_name])) | |
113 | except (KeyError, ValueError): | |
114 | val = default | |
115 | return val | |
116 | ||
109 | 117 | def _get_base_query(self, object_id=None): |
110 | try: | |
111 | confirmed = bool(json.loads(flask.request.args['confirmed'])) | |
112 | except (KeyError, ValueError): | |
113 | confirmed = None | |
114 | try: | |
115 | active = bool(json.loads(flask.request.args['active'])) | |
116 | query = Workspace.query_with_count( | |
117 | confirmed, | |
118 | active=active, | |
119 | workspace_name=object_id) | |
120 | except (KeyError, ValueError): | |
121 | query = Workspace.query_with_count( | |
122 | confirmed, | |
123 | workspace_name=object_id) | |
118 | confirmed = self._get_querystring_boolean_field('confirmed') | |
119 | active = self._get_querystring_boolean_field('active') | |
120 | readonly = self._get_querystring_boolean_field('readonly') | |
121 | query = Workspace.query_with_count( | |
122 | confirmed, | |
123 | active=active, | |
124 | readonly=readonly, | |
125 | workspace_name=object_id) | |
124 | 126 | return query |
125 | 127 | |
126 | 128 | def _get_object(self, object_id, eagerload=False, **kwargs): |
128 | 130 | Given the object_id and extra route params, get an instance of |
129 | 131 | ``self.model_class`` |
130 | 132 | """ |
131 | confirmed = flask.request.values.get('confirmed', None) | |
132 | if confirmed: | |
133 | try: | |
134 | confirmed = bool(int(confirmed)) | |
135 | except ValueError: | |
136 | if confirmed.lower() == 'false': | |
137 | confirmed = False | |
138 | elif confirmed.lower() == 'true': | |
139 | confirmed = True | |
133 | confirmed = self._get_querystring_boolean_field('confirmed') | |
134 | active = self._get_querystring_boolean_field('active') | |
140 | 135 | self._validate_object_id(object_id) |
141 | 136 | query = db.session.query(Workspace).filter_by(name=object_id) |
137 | if active is not None: | |
138 | query = query.filter_by(active=active) | |
142 | 139 | query = query.options( |
143 | 140 | with_expression( |
144 | 141 | Workspace.vulnerability_web_count, |
187 | 184 | self._report_ppath = os.path.join(self._report_path, "process") |
188 | 185 | self._report_upath = os.path.join(self._report_path, "unprocessed") |
189 | 186 | |
187 | if not os.path.exists(CONF.getReportPath()): | |
188 | os.mkdir(CONF.getReportPath()) | |
189 | ||
190 | 190 | if not os.path.exists(self._report_path): |
191 | 191 | os.mkdir(self._report_path) |
192 | 192 | |
220 | 220 | db.session.commit() |
221 | 221 | return changed |
222 | 222 | |
223 | @route('/<workspace_id>/change_readonly/', methods=["PUT"]) | |
224 | def change_readonly(self, workspace_id): | |
225 | self._get_object(workspace_id).change_readonly() | |
226 | db.session.commit() | |
227 | return self._get_object(workspace_id).readonly | |
228 | ||
223 | 229 | |
224 | 230 | WorkspaceView.register(workspace_api) |
5 | 5 | import os |
6 | 6 | import string |
7 | 7 | import datetime |
8 | from future.builtins import range # __future__ | |
8 | 9 | from os.path import join, expanduser |
9 | 10 | from random import SystemRandom |
10 | 11 | |
14 | 15 | |
15 | 16 | try: |
16 | 17 | # py2.7 |
17 | from configparser import ConfigParser, NoSectionError, NoOptionError | |
18 | from configparser import ConfigParser, NoSectionError, NoOptionError, DuplicateSectionError | |
18 | 19 | except ImportError: |
19 | 20 | # py3 |
20 | from ConfigParser import ConfigParser, NoSectionError, NoOptionError | |
21 | from ConfigParser import ConfigParser, NoSectionError, NoOptionError, DuplicateSectionError | |
21 | 22 | |
22 | 23 | import flask |
23 | 24 | from flask import Flask, session, g |
51 | 52 | os.mkdir(default_path) |
52 | 53 | config = ConfigParser() |
53 | 54 | config.read(server.config.LOCAL_CONFIG_FILE) |
54 | config.add_section('storage') | |
55 | config.set('storage', 'path', default_path) | |
55 | try: | |
56 | config.add_section('storage') | |
57 | config.set('storage', 'path', default_path) | |
58 | except DuplicateSectionError: | |
59 | logger.info('Duplicate section storage. skipping.') | |
56 | 60 | with open(server.config.LOCAL_CONFIG_FILE, 'w') as configfile: |
57 | 61 | config.write(configfile) |
58 | 62 | |
154 | 158 | config = ConfigParser() |
155 | 159 | config.read(LOCAL_CONFIG_FILE) |
156 | 160 | rng = SystemRandom() |
157 | secret_key = "".join([rng.choice(string.ascii_letters + string.digits) for _ in xrange(25)]) | |
161 | secret_key = "".join([rng.choice(string.ascii_letters + string.digits) for _ in range(25)]) | |
158 | 162 | app.config['SECRET_KEY'] = secret_key |
159 | config.set('faraday_server', 'secret_key', secret_key) | |
163 | try: | |
164 | config.set('faraday_server', 'secret_key', secret_key) | |
165 | except NoSectionError: | |
166 | config.add_section('faraday_server') | |
167 | config.set('faraday_server', 'secret_key', secret_key) | |
160 | 168 | with open(LOCAL_CONFIG_FILE, 'w') as configfile: |
161 | 169 | config.write(configfile) |
162 | 170 |
37 | 37 | field_display_name = click.prompt('Display name') |
38 | 38 | field_type = click.prompt('Field type (int, str, list)', type=click.Choice(['int', 'str', 'list'])) |
39 | 39 | custom_fields = db.session.query(CustomFieldsSchema) |
40 | ||
41 | #Checks the name of the fields wont be a duplicate | |
42 | for custom_field in custom_fields: | |
43 | if field_name == custom_field.field_name \ | |
44 | or field_display_name == custom_field.field_display_name: | |
45 | print('Custom field already exists, skipping') | |
46 | sys.exit(1) | |
47 | ||
40 | 48 | current_used_orders = set() |
49 | ||
41 | 50 | if custom_fields.count(): |
42 | 51 | print('Custom field current order') |
43 | 52 | for custom_field in custom_fields: |
59 | 68 | invalid_field_order = True |
60 | 69 | continue |
61 | 70 | invalid_field_order = False |
62 | confirmation = click.prompt('New CustomField will be added to vulnerability -> Order {order} ({0},{1},{2}) <-, confirm to continue (yes/no)'.format(field_name, field_display_name, field_type, order=field_order)) | |
71 | confirmation = click.prompt('New CustomField will be added to vulnerability -> Order {order} ({0},{1},{2}) <-, confirm to continue (yes/no)'\ | |
72 | .format(field_name, field_display_name, field_type, order=field_order)) | |
63 | 73 | if not confirmation: |
64 | 74 | sys.exit(1) |
65 | 75 |
11 | 11 | import sys |
12 | 12 | import click |
13 | 13 | import psycopg2 |
14 | from future.builtins import range # __future__ | |
14 | 15 | from random import SystemRandom |
15 | 16 | from tempfile import TemporaryFile |
16 | 17 | from subprocess import Popen, PIPE |
24 | 25 | FARADAY_BASE_CONFIG_XML, |
25 | 26 | FARADAY_BASE, |
26 | 27 | ) |
28 | from server.utils.database import is_unique_constraint_violation | |
27 | 29 | |
28 | 30 | try: |
29 | 31 | # py2.7 |
124 | 126 | already_created = False |
125 | 127 | try: |
126 | 128 | engine.execute("INSERT INTO \"faraday_user\" (username, name, password, " |
127 | "is_ldap, active, last_login_ip, current_login_ip, role) VALUES ('faraday', 'Administrator', " | |
128 | "'{0}', false, true, '127.0.0.1', '127.0.0.1', 'admin');".format(random_password)) | |
129 | except sqlalchemy.exc.IntegrityError: | |
130 | # when re using database user could be created previusly | |
131 | already_created = True | |
132 | print( | |
133 | "{yellow}WARNING{white}: Faraday administrator user already exists.".format( | |
134 | yellow=Fore.YELLOW, white=Fore.WHITE)) | |
129 | "is_ldap, active, last_login_ip, current_login_ip, role, state_otp) VALUES ('faraday', 'Administrator', " | |
130 | "'{0}', false, true, '127.0.0.1', '127.0.0.1', 'admin', 'disabled');".format(random_password)) | |
131 | except sqlalchemy.exc.IntegrityError as ex: | |
132 | if is_unique_constraint_violation(ex): | |
133 | # when re using database user could be created previously | |
134 | already_created = True | |
135 | print( | |
136 | "{yellow}WARNING{white}: Faraday administrator user already exists.".format( | |
137 | yellow=Fore.YELLOW, white=Fore.WHITE)) | |
138 | else: | |
139 | print( | |
140 | "{yellow}WARNING{white}: Can't create administrator user.".format( | |
141 | yellow=Fore.YELLOW, white=Fore.WHITE)) | |
142 | raise | |
135 | 143 | if not already_created: |
136 | 144 | |
137 | 145 | self._save_user_xml(random_password) |
174 | 182 | |
175 | 183 | def generate_random_pw(self, pwlen): |
176 | 184 | rng = SystemRandom() |
177 | return "".join([rng.choice(string.ascii_letters + string.digits) for _ in xrange(pwlen)]) | |
185 | return "".join([rng.choice(string.ascii_letters + string.digits) for _ in range(pwlen)]) | |
178 | 186 | |
179 | 187 | def _configure_new_postgres_user(self, psql_log_file): |
180 | 188 | """ |
238 | 246 | postgres_command = [] |
239 | 247 | |
240 | 248 | print('Creating database {0}'.format(database_name)) |
241 | command = postgres_command + ['createdb', '-O', username, database_name] | |
249 | command = postgres_command + ['createdb', '-E', 'utf8', '-O', username, database_name] | |
242 | 250 | p = Popen(command, stderr=psql_log_file, stdout=psql_log_file, cwd='/tmp') |
243 | 251 | p.wait() |
244 | 252 | return_code = p.returncode |
127 | 127 | section = ssl |
128 | 128 | elif section_name == 'storage': |
129 | 129 | section = storage |
130 | else: | |
131 | return | |
130 | 132 | section.parse(__parser) |
131 | 133 | |
132 | 134 |
11 | 11 | import logging |
12 | 12 | import datetime |
13 | 13 | import multiprocessing |
14 | ||
14 | from future.builtins import range # __future__ | |
15 | 15 | |
16 | 16 | import requests |
17 | 17 | from requests.exceptions import HTTPError, RequestException |
600 | 600 | rng = SystemRandom() |
601 | 601 | password = "".join( |
602 | 602 | [rng.choice(string.ascii_letters + string.digits) for _ in |
603 | xrange(12)]) | |
603 | range(12)]) | |
604 | 604 | creator, created = get_or_create(session, User, username=username) |
605 | 605 | if created: |
606 | 606 | creator.active = False |
1159 | 1159 | policy_violation_id = Column(Integer, ForeignKey('policy_violation_template.id'), primary_key=True) |
1160 | 1160 | |
1161 | 1161 | policy_violation = relationship("PolicyViolationTemplate", backref="policy_violation_template_associations", foreign_keys=[policy_violation_id]) |
1162 | vulnerability = relationship("VulnerabilityTemplate", backref="policy_violation_template_vulnerability_associations", | |
1162 | vulnerability = relationship("VulnerabilityTemplate", backref=backref("policy_violation_template_vulnerability_associations", cascade="all, delete-orphan"), | |
1163 | 1163 | foreign_keys=[vulnerability_id]) |
1164 | 1164 | |
1165 | 1165 | |
1317 | 1317 | customer = BlankColumn(String(250)) # TBI |
1318 | 1318 | description = BlankColumn(Text) |
1319 | 1319 | active = Column(Boolean(), nullable=False, default=True) # TBI |
1320 | readonly = Column(Boolean(), nullable=False, default=False) # TBI | |
1320 | 1321 | end_date = Column(DateTime(), nullable=True) |
1321 | 1322 | name = NonBlankColumn(String(250), unique=True, nullable=False) |
1322 | 1323 | public = Column(Boolean(), nullable=False, default=False) # TBI |
1338 | 1339 | cascade="all, delete-orphan") |
1339 | 1340 | |
1340 | 1341 | @classmethod |
1341 | def query_with_count(cls, confirmed, active=None, workspace_name=None): | |
1342 | def query_with_count(cls, confirmed, active=True, readonly=None, workspace_name=None): | |
1342 | 1343 | """ |
1343 | 1344 | Add count fields to the query. |
1344 | 1345 | |
1367 | 1368 | workspace.customer AS workspace_customer, |
1368 | 1369 | workspace.description AS workspace_description, |
1369 | 1370 | workspace.active AS workspace_active, |
1371 | workspace.readonly AS workspace_readonly, | |
1370 | 1372 | workspace.end_date AS workspace_end_date, |
1371 | 1373 | workspace.name AS workspace_name, |
1372 | 1374 | workspace.public AS workspace_public, |
1404 | 1406 | if active is not None: |
1405 | 1407 | filters.append(" workspace.active = :active ") |
1406 | 1408 | params['active'] = active |
1409 | if readonly is not None: | |
1410 | filters.append(" workspace.readonly = :readonly ") | |
1411 | params['readonly'] = readonly | |
1407 | 1412 | if workspace_name: |
1408 | 1413 | filters.append(" workspace.name = :workspace_name ") |
1409 | 1414 | params['workspace_name'] = workspace_name |
1411 | 1416 | query += ' WHERE ' + ' AND '.join(filters) |
1412 | 1417 | #query += " GROUP BY workspace.id " |
1413 | 1418 | query += " ORDER BY workspace.name ASC" |
1414 | return db.engine.execute(text(query), params) | |
1419 | return db.session.execute(text(query), params) | |
1415 | 1420 | |
1416 | 1421 | def set_scope(self, new_scope): |
1417 | 1422 | return set_children_objects(self, new_scope, |
1428 | 1433 | # else: |
1429 | 1434 | # raise Cannot exceed or return false |
1430 | 1435 | |
1431 | ||
1432 | 1436 | def deactivate(self): |
1433 | 1437 | if self.active is not False: |
1434 | 1438 | self.active = False |
1435 | 1439 | return True |
1436 | 1440 | return False |
1441 | ||
1442 | def change_readonly(self): | |
1443 | self.readonly = not self.readonly | |
1437 | 1444 | |
1438 | 1445 | |
1439 | 1446 | class Scope(Metadata): |
310 | 310 | def is_unique_constraint_violation(exception): |
311 | 311 | from server.models import db |
312 | 312 | if db.engine.dialect.name != 'postgresql': |
313 | # Not implemened for RDMS other than postgres, we can't live without | |
314 | # this, it is just an extra check | |
313 | # Not implemened for RDMS other than postgres, we can live without | |
314 | # this since it is just an extra check | |
315 | 315 | return True |
316 | 316 | assert isinstance(exception.orig.pgcode, str) |
317 | 317 | return exception.orig.pgcode == UNIQUE_VIOLATION |
520 | 520 | |
521 | 521 | .last-item-field { |
522 | 522 | margin-bottom: 25px; |
523 | } | |
524 | ||
525 | ||
526 | /* ANGULAR SIMPLE PAGINATION */ | |
527 | ||
528 | .simple-pagination p { | |
529 | display: inline-block; | |
530 | line-height: 24px; | |
531 | margin: 0 10px 0 0; | |
532 | } | |
533 | ||
534 | .simple-pagination__button { | |
535 | background: #fff; | |
536 | border: none; | |
537 | padding: 0px 6px; | |
538 | } | |
539 | ||
540 | button[disabled], html input[disabled] { | |
541 | cursor: default!important; | |
542 | color: #ddd; | |
543 | } | |
544 | ||
545 | .simple-pagination__page-limit__option:after { | |
546 | content: ' | '; | |
547 | } | |
548 | ||
549 | .simple-pagination__page-limit .active { | |
550 | font-weight: bold; | |
551 | } | |
552 | ||
553 | .simple-pagination { | |
554 | text-align: center; | |
555 | margin-bottom: 15px; | |
556 | } | |
557 | ||
558 | .toogle-img-container { | |
559 | width: 46px; | |
523 | 560 | }⏎ |
100 | 100 | <script type="text/javascript" src="scripts/commons/directives/osintLink.js"></script> |
101 | 101 | <script type="text/javascript" src="scripts/commons/directives/file.js"></script> |
102 | 102 | <script type="text/javascript" src="scripts/commons/directives/fileModel.js"></script> |
103 | <script type="text/javascript" src="scripts/commons/directives/angular-simple-pagination.js"></script> | |
103 | 104 | <script type="text/javascript" src="scripts/commons/controllers/modal.js"></script> |
104 | 105 | <script type="text/javascript" src="scripts/commons/controllers/headerCtrl.js"></script> |
105 | 106 | <script type="text/javascript" src="scripts/commons/controllers/commercialCtrl.js"></script> |
11 | 11 | 'filter', 'angular-clipboard', 'ngCookies', 'cfp.hotkeys', 'chart.js', |
12 | 12 | 'ui.grid', 'ui.grid.selection', 'ui.grid.grouping', 'ngSanitize', |
13 | 13 | 'ui.grid.pagination', 'ui.grid.pinning', 'angularMoment', 'ui-notification', |
14 | 'ui.grid.resizeColumns']) | |
14 | 'ui.grid.resizeColumns', 'angularSimplePagination']) | |
15 | 15 | .constant("BASEURL", (function() { |
16 | 16 | var url = window.location.origin + "/"; |
17 | 17 | return url; |
9 | 9 | </div> |
10 | 10 | </div> |
11 | 11 | <div class="forbidden-error"> |
12 | <h3>You don't have the permissions or It is either read-protected (workspace disabled)</h3> | |
12 | <h3>You don't have the permissions or It is either read-protected (readonly workspace)</h3> | |
13 | 13 | </div> |
14 | 14 | </div> |
15 | 15 | </div><!-- #reports-main --></div><!-- .right-main --> |
0 | 'use strict'; | |
1 | ||
2 | angular.module('angularSimplePagination', []).directive('simplePagination', SimplePagination); | |
3 | ||
4 | function SimplePagination() { | |
5 | return { | |
6 | restrict: 'E', | |
7 | scope: { | |
8 | currentPage: '=', | |
9 | offset: '=', | |
10 | pageLimit: '=', | |
11 | pageLimits: '=', | |
12 | total: '=', | |
13 | onUpdate: '&' | |
14 | }, | |
15 | bindToController: true, | |
16 | controller: SimplePaginationController, | |
17 | controllerAs: 'pagination', | |
18 | template: '\n <div class="simple-pagination">\n <p class="simple-pagination__items">Showing {{pagination.pageLimit}} out of {{pagination.total}}</p>\n <p>\n <button ng-click="pagination.previousPage()" ng-disabled="pagination.currentPage <= 0" class="simple-pagination__button simple-pagination__button--prev">\n ❮\n </button>\n <span class="simple-pagination__pages">{{pagination.currentPage + 1}} of {{pagination.getTotalPages()}}</span>\n <button ng-click="pagination.nextPage()" ng-disabled="pagination.currentPage === (pagination.getTotalPages() - 1)" class="simple-pagination__button simple-pagination__button--next">\n ❯\n </button>\n </p>\n <p class="simple-pagination__page-limit">\n <span class="simple-pagination__page-limit__option" ng-repeat="limit in pagination.pageLimits" ng-if="limit < pagination.total">\n <a href="" ng-click="pagination.setItemsPerPages(limit)" ng-class="{\'active\': pagination.isCurrentPageLimit(limit)}">{{limit}}</a>\n </span>\n <span>\n <a href="" ng-click="pagination.setItemsPerPages(pagination.total)" ng-class="{\'active\': pagination.isCurrentPageLimit(pagination.total)}">All</a>\n </span>\n </p>\n </div>\n ' | |
19 | }; | |
20 | } | |
21 | ||
22 | function SimplePaginationController() { | |
23 | var self = this; | |
24 | ||
25 | self.currentPage = self.currentPage || 0; | |
26 | self.pageLimit = self.pageLimit || self.pageLimits[0]; | |
27 | ||
28 | self.setItemsPerPages = function (max) { | |
29 | self.pageLimit = max >= self.total ? self.total : max; | |
30 | self.currentPage = 0; | |
31 | self.offset = 0; | |
32 | invokeCallback(); | |
33 | }; | |
34 | ||
35 | self.nextPage = function () { | |
36 | self.currentPage += 1; | |
37 | self.offset = self.currentPage * self.pageLimit; | |
38 | invokeCallback(); | |
39 | }; | |
40 | ||
41 | self.previousPage = function () { | |
42 | self.currentPage -= 1; | |
43 | self.offset = self.currentPage * self.pageLimit; | |
44 | invokeCallback(); | |
45 | }; | |
46 | ||
47 | self.getTotalPages = function () { | |
48 | return Math.ceil(self.total / self.pageLimit); | |
49 | }; | |
50 | ||
51 | self.isCurrentPageLimit = function (value) { | |
52 | return self.pageLimit == value; | |
53 | }; | |
54 | ||
55 | function invokeCallback() { | |
56 | if (self.onUpdate) { | |
57 | self.onUpdate(); | |
58 | } | |
59 | } | |
60 | }⏎ |
13 | 13 | <table class="table-v3 licenses-list table table-responsive"> |
14 | 14 | <thead> |
15 | 15 | <tr class="ui-grid-header"> |
16 | <th class="ui-grid-cell-contents ui-grid-header-cell">Read only</th> | |
16 | 17 | <th class="ui-grid-cell-contents ui-grid-header-cell">Active</th> |
17 | 18 | <th class="ui-grid-cell-contents ui-grid-header-cell">Name</th> |
18 | 19 | <th class="ui-grid-cell-contents ui-grid-header-cell">Vulns</th> |
23 | 24 | <tbody> |
24 | 25 | <tr ng-repeat="ws in workspaces"> |
25 | 26 | <td class="ui-grid-cell-contents active-toggle"> |
26 | <span ng-click="activeToggle(ws)" class="active-toggle-container" ng-class="{ disabled:ws.active === false }" uib-tooltip="{{(ws.active === true) ? 'Disable workspace' : 'Enable workspace'}}" tooltip-placement="right"> | |
27 | <img ng-src="{{ (ws.active) ? 'images/icon-list-confirmed.svg' : 'images/icon-list-notconfirmed.svg'}}" class="confirm-icon" ng-style="{ 'opacity': (ws.active === true) ? '1' : '0.7' }" /> | |
28 | </span> | |
27 | <div class="toogle-img-container"> | |
28 | <span ng-click="readonlyToggle(ws)" class="active-toggle-container" ng-class="{ disabled:ws.readonly === false }" uib-tooltip="{{(ws.readonly === false) ? 'Workspace with all permissions' : 'Workspace read only'}}" tooltip-placement="right"> | |
29 | <img ng-src="{{ (ws.readonly == true) ? 'images/icon-list-confirmed.svg' : 'images/icon-list-notconfirmed.svg'}}" class="confirm-icon" ng-style="{ 'opacity': (ws.readonly === true) ? '1' : '0.7' }" /> | |
30 | </span> | |
31 | </div> | |
32 | </td> | |
33 | ||
34 | <td class="ui-grid-cell-contents active-toggle"> | |
35 | <div class="toogle-img-container"> | |
36 | <span ng-click="activeToggle(ws)" class="active-toggle-container" ng-class="{ disabled:ws.active === false }" uib-tooltip="{{(ws.active === true) ? 'Disable workspace' : 'Enable workspace'}}" tooltip-placement="right"> | |
37 | <img ng-src="{{ (ws.active) ? 'images/icon-list-confirmed.svg' : 'images/icon-list-notconfirmed.svg'}}" class="confirm-icon" ng-style="{ 'opacity': (ws.active === true) ? '1' : '0.7' }" /> | |
38 | </span> | |
39 | </div> | |
29 | 40 | </td> |
30 | 41 | <td class="ui-grid-cell-contents"> |
31 | 42 | <span class="onhover upsize" ng-click="redirect(ws.name)"> |
417 | 417 | var putUrl = APIURL + "ws/" + wsName + "/deactivate/"; |
418 | 418 | return send_data(putUrl, undefined, false, "PUT"); |
419 | 419 | } |
420 | ||
421 | ServerAPI.readOnlyToogle = function (wsName) { | |
422 | var putUrl = APIURL + "ws/" + wsName + "/change_readonly/"; | |
423 | return send_data(putUrl, undefined, false, "PUT"); | |
424 | } | |
425 | ||
420 | 426 | |
421 | 427 | ServerAPI.getWorkspaceSummary = function (wsName, confirmed) { |
422 | 428 |
9 | 9 | |
10 | 10 | <div class="reports col-md-12 col-sm-12 col-xs-12"> |
11 | 11 | <div class="button-control justify-flex-start col-md-12 col-sm-12 col-xs-12"> |
12 | ||
12 | 13 | <div class="control-wrapper control-new"> |
13 | <button id="new" type="button" class="btn btn-success btn-new" ng-title="{{workspaceData.active ? 'New credential' : 'Read-only. Workspace disabled'}}" ng-if="parentObject.parent_type" ng-click="new()" ng-disabled="!workspaceData.active"> | |
14 | <button id="new" type="button" class="btn btn-success btn-new" ng-title="{{workspaceData.readonly == false? 'New credential' : 'Read-only. Workspace disabled'}}" ng-if="parentObject.parent_type" ng-click="new()" ng-disabled="workspaceData.readonly == true"> | |
14 | 15 | New |
15 | 16 | </button> |
16 | 17 | </div> |
17 | 18 | <div class="control-wrapper control-edit"> |
18 | <button type="button" class="btn edit" title="{{workspaceData.active ? 'Edit selected credentials' : 'Read-only. Workspace disabled'}}" ng-disabled="selectedCredentials().length != 1 || !workspaceData.active" ng-click="edit()"> | |
19 | <button type="button" class="btn edit" title="{{workspaceData.readonly == false ? 'Edit selected credentials' : 'Read-only. Workspace disabled'}}" ng-disabled="selectedCredentials().length != 1 || workspaceData.readonly == true" ng-click="edit()"> | |
19 | 20 | <img src="images/icon-toolbar-edit.svg" class="edit-icon" /> |
20 | 21 | </button> |
21 | 22 | </div> |
22 | 23 | <div class="control-wrapper"> |
23 | <button id="delete" type="button" class="btn" title="{{workspaceData.active ? 'Delete selected credentials' : 'Read-only. Workspace disabled'}}" ng-disabled="selectedCredentials().length == 0 || !workspaceData.active" ng-click="delete()"> | |
24 | <button id="delete" type="button" class="btn" title="{{workspaceData.readonly == false ? 'Delete selected credentials' : 'Read-only. Workspace disabled'}}" ng-disabled="selectedCredentials().length == 0 || workspaceData.readonly == true" ng-click="delete()"> | |
24 | 25 | <img src="images/icon-toolbar-delete.svg" class="delete-icon" /> |
25 | 26 | </button> |
26 | 27 | </div> |
13 | 13 | |
14 | 14 | // Get last 15 commands |
15 | 15 | var init = function () { |
16 | ||
17 | $scope.settings = { | |
18 | currentPage: 0, | |
19 | offset: 0, | |
20 | pageLimit: 5, | |
21 | pageLimits: ['3', '5', '10', '20', '30', '50', '80', '100'] | |
22 | }; | |
23 | ||
16 | 24 | if ($routeParams.wsId !== undefined) { |
17 | 25 | $scope.workspace = $routeParams.wsId; |
18 | ||
19 | $scope.isExpanded = false; | |
20 | $scope.hideEmpty = false; | |
21 | 26 | |
22 | 27 | collapse(); |
23 | 28 | |
37 | 42 | }; |
38 | 43 | |
39 | 44 | var collapse = function () { |
40 | $scope.cmdLimit = 5; | |
45 | $scope.settings.pageLimit = 5; | |
41 | 46 | $scope.isExpanded = false; |
42 | $scope.hideEmpty = false; | |
47 | $scope.hideEmpty = true; | |
43 | 48 | angular.element('#first-row-panel').css('display', 'inherit'); |
44 | 49 | angular.element('#activities-container-row').addClass('mt-md'); |
45 | 50 | }; |
46 | 51 | |
47 | 52 | var expand = function () { |
48 | $scope.cmdLimit = 15; // Should be a constant | |
53 | $scope.settings.pageLimit = 15; | |
49 | 54 | $scope.isExpanded = true; |
55 | $scope.hideEmpty = true; | |
50 | 56 | angular.element('#first-row-panel').css('display', 'none'); |
51 | 57 | angular.element('#activities-container-row').removeClass('mt-md'); |
52 | 58 | }; |
55 | 61 | return cmd.hosts_count === 0 && cmd.services_count === 0 && cmd.vulnerabilities_count === 0; |
56 | 62 | }; |
57 | 63 | |
64 | $scope.getValidCount = function () { | |
65 | var count = 0; | |
66 | for(var i = 0; i < vm.commands.length; i++){ | |
67 | if (!$scope.isEmpty(vm.commands[i])) count ++ | |
68 | } | |
69 | return count; | |
70 | }; | |
71 | ||
58 | 72 | dashboardSrv.registerCallback(init); |
59 | 73 | init(); |
60 | 74 | }]);⏎ |
19 | 19 | $scope.commands = commands; |
20 | 20 | }); |
21 | 21 | } |
22 | ||
23 | $scope.settings = { | |
24 | currentPage: 0, | |
25 | offset: 0, | |
26 | pageLimit: 5, | |
27 | pageLimits: ['5', '10', '20', '30', '50', '80', '100'] | |
28 | }; | |
22 | 29 | }; |
23 | 30 | |
24 | 31 | // toggles sort field and order |
21 | 21 | $scope.loadData(); |
22 | 22 | }, true); |
23 | 23 | } |
24 | ||
25 | $scope.settings = { | |
26 | currentPage: 0, | |
27 | offset: 0, | |
28 | pageLimit: 5, | |
29 | pageLimits: ['3', '5', '10', '20', '30', '50', '80', '100'] | |
30 | }; | |
24 | 31 | }; |
25 | 32 | |
26 | 33 | $scope.loadData = function() { |
0 | 0 | <article class='panel left-big-box' ng-controller="activityFeedCtrl as activityFeed"> |
1 | <header ng-init="cmdLimit = 5"> | |
1 | <header> | |
2 | 2 | <h2>Activity Feed |
3 | 3 | <span class="glyphicon glyphicon-info-sign" uib-tooltip="Faraday feed"></span> |
4 | 4 | |
29 | 29 | |
30 | 30 | |
31 | 31 | </header> |
32 | <div ng-if="activityFeed.commands.length === 0" class="no-info-overlay"> | |
32 | <div ng-if="activityFeed.commands.length === 0 || (getValidCount() === 0 && hideEmpty == true)" class="no-info-overlay"> | |
33 | 33 | <p class="no-info-text">No activities found yet.</p> |
34 | 34 | </div> |
35 | 35 | <div class="ph-xl"> |
36 | 36 | <table id="commands" ng-if="activityFeed.commands.length > 0" class="tablesorter table table-striped"> |
37 | 37 | <tbody> |
38 | <tr ng-repeat="cmd in activityFeed.commands | orderBy : '-date' | limitTo:cmdLimit" | |
38 | <tr ng-repeat="cmd in activityFeed.commands | orderBy : '-date' | limitTo:settings.pageLimit:settings.offset" | |
39 | 39 | ng-show="!isEmpty(cmd) || !hideEmpty"> |
40 | 40 | <td align="left" ng-class="{'hide-border-top':$first}"> |
41 | 41 | <span ng-if="cmd.import_source == 'report'"> |
66 | 66 | </tr> |
67 | 67 | </tbody> |
68 | 68 | </table> |
69 | ||
70 | <simple-pagination ng-if="isExpanded == true && hideEmpty == false" | |
71 | current-page="settings.currentPage" | |
72 | offset="settings.offset" | |
73 | page-limit="settings.pageLimit" | |
74 | page-limits="settings.pageLimits" | |
75 | total="activityFeed.commands.length"> | |
76 | </simple-pagination> | |
77 | ||
69 | 78 | </div> |
70 | 79 | </article> |
9 | 9 | <div class="ph-xl"> |
10 | 10 | <table id="" ng-if="commands.length > 0" class="tablesorter table table-striped last-vuln text-left"> |
11 | 11 | <thead> |
12 | <tr> | |
13 | <th class="text-left"><span ng-click="cmdToggleSort('user')">By</span></th> | |
14 | <th class="text-left"><span ng-click="cmdToggleSort('command')">Command</span></th> | |
15 | <th class="text-left"><span ng-click="cmdToggleSort('date')">Start Date</span></th> | |
16 | <th class="text-left"><span ng-click="cmdToggleSort('duration')">Duration</span></th> | |
17 | </tr> | |
12 | <tr> | |
13 | <th class="text-left"><span ng-click="cmdToggleSort('user')">By</span></th> | |
14 | <th class="text-left"><span ng-click="cmdToggleSort('command')">Command</span></th> | |
15 | <th class="text-left"><span ng-click="cmdToggleSort('date')">Start Date</span></th> | |
16 | <th class="text-left"><span ng-click="cmdToggleSort('duration')">Duration</span></th> | |
17 | </tr> | |
18 | 18 | </thead> |
19 | 19 | <tbody> |
20 | <tr ng-repeat="cmd in commands | orderObjectBy:cmdSortField:cmdSortReverse track by $index"> | |
21 | <td> | |
22 | <b><p uib-tooltip="{{cmd.ip}}">{{cmd.user}}@{{cmd.hostname}}</p></b> | |
23 | </td> | |
24 | <td class="wrapword"> | |
25 | <span ng-if="cmd.import_source == 'report'">Import {{(cmd.tool == 'unknown' ? cmd.command : cmd.tool) + ': ' + cmd.params}}</span> | |
26 | <span ng-if="cmd.import_source == 'shell'">{{cmd.command + ' ' + cmd.params}}</span> | |
27 | </td> | |
28 | <td am-time-ago="cmd.date" am-preprocess="utc"></td> | |
29 | <td ng-bind="cmd.duration || 'undefined'"></td> | |
30 | </tr> | |
20 | <tr ng-repeat="cmd in commands | limitTo:settings.pageLimit:settings.offset | orderObjectBy:cmdSortField:cmdSortReverse track by $index "> | |
21 | <td> | |
22 | <b><p uib-tooltip="{{cmd.ip}}">{{cmd.user}}@{{cmd.hostname}}</p></b> | |
23 | </td> | |
24 | <td class="wrapword"> | |
25 | <span ng-if="cmd.import_source == 'report'">Import {{(cmd.tool == 'unknown' ? cmd.command : cmd.tool) + ': ' + cmd.params}}</span> | |
26 | <span ng-if="cmd.import_source == 'shell'">{{cmd.command + ' ' + cmd.params}}</span> | |
27 | </td> | |
28 | <td am-time-ago="cmd.date" am-preprocess="utc"></td> | |
29 | <td ng-bind="cmd.duration || 'undefined'"></td> | |
30 | </tr> | |
31 | 31 | </tbody> |
32 | 32 | </table> |
33 | ||
34 | <simple-pagination | |
35 | current-page="settings.currentPage" | |
36 | offset="settings.offset" | |
37 | page-limit="settings.pageLimit" | |
38 | page-limits="settings.pageLimits" | |
39 | total="commands.length"> | |
40 | </simple-pagination> | |
41 | ||
33 | 42 | </div> |
34 | 43 | </article> |
18 | 18 | </tr> |
19 | 19 | </thead> |
20 | 20 | <tbody> |
21 | <tr ng-repeat="vuln in vulns"> | |
21 | <tr ng-repeat="vuln in vulns | limitTo:settings.pageLimit:settings.offset"> | |
22 | 22 | <td><a href="" ng-click="navigate('/status/ws/'+workspace+'/search/severity='+vuln.severity)"><span class="label vuln fondo-{{vuln.severity}}">{{vuln.severity | uppercase}}</span></a></td> |
23 | 23 | <td> |
24 | 24 | <a href="" ng-click="navigate('/status/ws/'+workspace+'/search/target='+vuln.target)">{{vuln.target}}</a> |
30 | 30 | </tr> |
31 | 31 | </tbody> |
32 | 32 | </table> |
33 | ||
34 | <simple-pagination | |
35 | current-page="settings.currentPage" | |
36 | offset="settings.offset" | |
37 | page-limit="settings.pageLimit" | |
38 | page-limits="settings.pageLimits" | |
39 | total="vulns.length"> | |
40 | </simple-pagination> | |
41 | ||
33 | 42 | </div> |
34 | 43 | </article> |
269 | 269 | resolve: { |
270 | 270 | service: function() { |
271 | 271 | return $scope.selectedServices(); |
272 | }, | |
273 | workspace: function () { | |
274 | return $scope.workspaceData; | |
272 | 275 | } |
273 | 276 | } |
274 | 277 | }); |
8 | 8 | <div id="reports-main" class="fila clearfix"> |
9 | 9 | <div class="button-control col-md-12 col-sm-12 col-xs-12"> |
10 | 10 | <div class="control-wrapper control-new"> |
11 | <button type="button" class="btn btn-success btn-new" title="{{workspaceData.active == false ? 'Read-only. Workspace disabled': 'New hosts'}}" ng-click="new()" ng-disabled="workspaceData.active === false"> | |
11 | <button type="button" class="btn btn-success btn-new" title="{{workspaceData.readonly == true ? 'Read-only. Workspace disabled': 'New hosts'}}" ng-click="new()" ng-disabled="workspaceData.readonly === true"> | |
12 | 12 | New |
13 | 13 | </button> |
14 | 14 | </div> |
15 | 15 | <div class="control-wrapper control-edit"> |
16 | <button type="button" class="btn btn-default edit" title="{{workspaceData.active == false ? 'Read-only. Workspace disabled': 'Edit selected hosts'}}" ng-disabled="selectedHosts().length > 1 || workspaceData.active === false" ng-click="edit()"> | |
16 | <button type="button" class="btn btn-default edit" title="{{workspaceData.readonly == true ? 'Read-only. Workspace disabled': 'Edit selected hosts'}}" ng-disabled="selectedHosts().length > 1" ng-click="edit()"> | |
17 | 17 | <img src="images/icon-toolbar-edit.svg" class="edit-icon" /> |
18 | 18 | </button> |
19 | 19 | </div> |
20 | 20 | <div class="control-wrapper"> |
21 | <button type="button" class="btn btn-default" title="{{workspaceData.active == false ? 'Read-only. Workspace disabled': 'Delete selected items'}}" ng-click="delete()" ng-disabled="workspaceData.active === false"> | |
21 | <button type="button" class="btn btn-default" title="{{workspaceData.readonly == true ? 'Read-only. Workspace disabled': 'Delete selected items'}}" ng-click="delete()" ng-disabled="workspaceData.readonly === true"> | |
22 | 22 | <img src="images/icon-toolbar-delete.svg" class="delete-icon" /> |
23 | 23 | </button> |
24 | 24 | </div> |
79 | 79 | <div class="modal-footer"> |
80 | 80 | <div class="modal-button"> |
81 | 81 | <button class="btn btn-danger" ng-click="cancel()">Cancel</button> |
82 | <button class="btn btn-success" ng-disabled="form.$invalid" ng-click="ok()">OK</button> | |
82 | <button class="btn btn-success" ng-disabled="form.$invalid || workspace.readonly == true" ng-click="ok()">OK</button> | |
83 | 83 | </div> |
84 | 84 | </div> |
85 | 85 | </form> |
2 | 2 | <!-- See the file 'doc/LICENSE' for the license information --> |
3 | 3 | |
4 | 4 | <div class="modal-header"> |
5 | <h4 class="modal-title">This workspace does not exist. Select one of the list</h4> | |
5 | <h3 class="modal-title"><p>This workspace does not exist.</p></h3><h4><p></This> Select one of the list or create a <a ng-click="cancel()" href="#/workspaces">new</a> one.</p></h4> | |
6 | 6 | </div> |
7 | 7 | <div class="modal-body"> |
8 | 8 | <ul class="ws-list"> |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | 5 | .controller('serviceModalEdit', |
6 | ['$q', '$scope', '$modalInstance', '$routeParams', 'SERVICE_STATUSES', 'service', 'servicesManager', 'commonsFact', | |
7 | function($q, $scope, $modalInstance, $routeParams, SERVICE_STATUSES, service, servicesManager, commonsFact) { | |
6 | ['$q', '$scope', '$modalInstance', '$routeParams', 'SERVICE_STATUSES', 'service', 'servicesManager', 'commonsFact', 'workspace', | |
7 | function($q, $scope, $modalInstance, $routeParams, SERVICE_STATUSES, service, servicesManager, commonsFact, workspace) { | |
8 | 8 | |
9 | 9 | init = function() { |
10 | 10 | // current Workspace |
11 | 11 | var ws = $routeParams.wsId; |
12 | $scope.workspace = workspace; | |
12 | 13 | |
13 | 14 | if(service.length == 1) { |
14 | 15 | $scope.data = { |
10 | 10 | <h3>Services for host {{hostName}}</h3> |
11 | 11 | <div class="button-control col-md-12 col-sm-12 col-xs-12"> |
12 | 12 | <div class="control-wrapper control-new"> |
13 | <button type="button" class="btn btn-success btn-new" title="New service" ng-click="new()" style="padding:0px 0px" ng-disabled="workspaceData.active === false"> | |
13 | <button type="button" class="btn btn-success btn-new" title="{{workspaceData.readonly == true ? 'Read-only. Workspace disabled': 'New service'}}" ng-click="new()" style="padding:0px 0px" ng-disabled="workspaceData.readonly === true"> | |
14 | 14 | New service |
15 | 15 | </button> |
16 | 16 | </div> |
17 | 17 | <div class="control-wrapper control-edit"> |
18 | <button type="button" class="btn" title="Edit selected services" ng-click="edit()" ng-disabled="services.length == 0 || workspaceData.active === false"> | |
18 | <button type="button" class="btn" title="{{workspaceData.readonly == true ? 'Read-only. Workspace disabled': 'Edit selected services'}}" ng-click="edit()" ng-disabled="services.length == 0"> | |
19 | 19 | <img src="images/icon-toolbar-edit.svg" class="edit-icon" /> |
20 | 20 | </button> |
21 | 21 | </div> |
22 | 22 | <div class="control-wrapper"> |
23 | <button type="button" class="btn" title="Delete selected services" ng-click="delete()" ng-disabled="services.length == 0 || workspaceData.active === false"> | |
23 | <button type="button" class="btn" title="{{workspaceData.readonly == true ? 'Read-only. Workspace disabled': 'Delete selected services'}}" ng-click="delete()" ng-disabled="services.length == 0 || workspaceData.readonly === true"> | |
24 | 24 | <img src="images/icon-toolbar-delete.svg" class="delete-icon" /> |
25 | 25 | </button> |
26 | 26 | </div> |
131 | 131 | <h3> |
132 | 132 | Host details |
133 | 133 | <span style="float:right"> |
134 | <a class="btn btn-sm btn-default" href="#/host/ws/{{workspace}}/hid/{{host.id}}/edit" ng-if="!editing" ng-disabled="workspaceData.active === false"> | |
134 | <a class="btn btn-sm btn-default" href="#/host/ws/{{workspace}}/hid/{{host.id}}/edit" ng-if="!editing"> | |
135 | 135 | <span class="glyphicon glyphicon-pencil"></span> |
136 | 136 | Edit |
137 | 137 | </a> |
138 | <button type="button" class="btn btn-sm btn-default" title="Delete host" ng-click="deleteHost()" ng-if="!editing" ng-disabled="workspaceData.active === false"> | |
138 | <button type="button" class="btn btn-sm btn-default" title="{{workspaceData.readonly == true ? 'Read-only. Workspace disabled': 'Delete host'}}" ng-click="deleteHost()" ng-if="!editing" ng-disabled="workspaceData.readonly === true"> | |
139 | 139 | <span class="glyphicon glyphicon-trash"></span> |
140 | 140 | Delete |
141 | 141 | </button> |
142 | 142 | <a class="btn btn-danger" href="#/host/ws/{{workspace}}/hid/{{host.id}}" ng-click="loadHosts()" ng-if="editing && !creating">Cancel</a> |
143 | <button class="btn btn-success" ng-disabled="form.$invalid" ng-click="ok()" ng-if="editing">OK</button> | |
143 | <button class="btn btn-success" ng-disabled="form.$invalid || workspaceData.readonly == true" ng-click="ok()" ng-if="editing">OK</button> | |
144 | 144 | </span> |
145 | 145 | </h3> |
146 | 146 | <div class="form-horizontal"> |
219 | 219 | <div style="float: right"> |
220 | 220 | <a class="btn btn-danger" href="#/host/ws/{{workspace}}/hid/{{host.id}}" ng-click="loadHosts()" ng-if="editing && !creating">Cancel</a> |
221 | 221 | <a class="btn btn-danger" href="#/hosts/ws/{{workspace}}" ng-if="editing && creating">Cancel</a> |
222 | <button class="btn btn-success" ng-disabled="form.$invalid" ng-click="ok()" ng-if="editing">OK</button> | |
222 | <button class="btn btn-success" ng-disabled="form.$invalid || workspaceData.readonly == true" ng-click="ok()" ng-if="editing">OK</button> | |
223 | 223 | </div> |
224 | 224 | </form> |
225 | 225 | </div> |
101 | 101 | </div><!-- .modal-body --> |
102 | 102 | <div class="modal-footer"> |
103 | 103 | <div class="modal-button"> |
104 | <button class="btn btn-success" ng-disabled="form.$invalid" ng-click="ok()">OK</button> | |
104 | <button class="btn btn-success" ng-disabled="form.$invalid || workspace.readonly === true" ng-click="ok()">OK</button> | |
105 | 105 | <button class="btn btn-danger" ng-click="cancel()">Cancel</button> |
106 | 106 | </div> |
107 | 107 | </div> |
5 | 5 | .controller('modalEditCtrl', |
6 | 6 | ['$modalInstance', '$routeParams', 'EASEOFRESOLUTION', 'STATUSES', 'commonsFact', |
7 | 7 | 'BASEURL', 'severities', 'vuln', 'vulnModelsManager', 'vulnsManager', 'referenceFact', |
8 | 'encodeURIComponentFilter', 'customFields', | |
8 | 'encodeURIComponentFilter', 'customFields', 'workspace', | |
9 | 9 | function ($modalInstance, $routeParams, EASEOFRESOLUTION, STATUSES, commonsFact, |
10 | 10 | BASEURL, severities, vuln, vulnModelsManager, vulnsManager, referenceFact, |
11 | encodeURIComponent, customFields) { | |
11 | encodeURIComponent, customFields, workspace) { | |
12 | 12 | |
13 | 13 | var vm = this; |
14 | 14 | |
47 | 47 | vm.file_name_error = false; |
48 | 48 | |
49 | 49 | vm.customFields = customFields; |
50 | vm.workspace = workspace; | |
50 | 51 | |
51 | 52 | vm.data = { |
52 | 53 | _id: undefined, |
67 | 68 | severity: undefined, |
68 | 69 | method: "", |
69 | 70 | path: "", |
71 | status_code: undefined, | |
70 | 72 | pname: "", |
71 | 73 | params: "", |
72 | 74 | query: "", |
77 | 77 | params: "", |
78 | 78 | parents: [], // a tuple with (parent_id, parent_type) |
79 | 79 | path: "", |
80 | status_code: undefined, | |
80 | 81 | pname: "", |
81 | 82 | policyviolations: [], |
83 | easeofresolution: null, | |
82 | 84 | query: "", |
83 | 85 | refs: [], |
84 | 86 | request: "", |
102 | 102 | } |
103 | 103 | }); |
104 | 104 | |
105 | $scope.gridApi.selection.on.rowFocusChanged( $scope, function ( rowChanged ) { | |
106 | $cookies.remove("selectedVulns"); | |
107 | }); | |
108 | ||
105 | 109 | $scope.gridApi.pagination.on.paginationChanged($scope, function (pageNumber, pageSize) { |
106 | 110 | // Save new page size in cookie |
107 | 111 | $cookies.put("pageSize", pageSize); |
128 | 132 | $scope.gridApi.core.on.rowsRendered($scope, function() { |
129 | 133 | resizeGrid(); |
130 | 134 | recalculateLastVisibleColSize(); |
135 | selectRowsByCookie(); | |
131 | 136 | }); |
132 | 137 | |
133 | 138 | $scope.gridApi.colResizable.on.columnSizeChanged($scope, function (colDef, deltaChange) { |
202 | 207 | "status": true, |
203 | 208 | "website": false, |
204 | 209 | "path": false, |
210 | "status_code": false, | |
205 | 211 | "request": false, |
206 | 212 | "refs": false, |
207 | 213 | "evidence": false, |
235 | 241 | "status": "100", |
236 | 242 | "website": "90", |
237 | 243 | "path": "90", |
244 | "status_code": "90", | |
238 | 245 | "request": "90", |
239 | 246 | "refs": "20", |
240 | 247 | "_attachments": "100", |
278 | 285 | angular.element($window).bind("resize", function () { |
279 | 286 | resizeGrid(); |
280 | 287 | }); |
281 | }; | |
282 | ||
288 | ||
289 | $cookies.remove("selectedVulns"); | |
290 | }; | |
291 | ||
292 | ||
293 | var selectRowsByCookie = function () { | |
294 | var selectedVulns = $cookies.getObject("selectedVulns"); | |
295 | if (selectedVulns !== undefined) { | |
296 | for (var i = 0; i < selectedVulns.length; i++){ | |
297 | for (var j = 0; j < $scope.gridOptions.data.length;j++){ | |
298 | if (selectedVulns[i] === $scope.gridOptions.data[j]._id){ | |
299 | $scope.gridApi.selection.selectRow($scope.gridOptions.data[j]); | |
300 | } | |
301 | } | |
302 | } | |
303 | } | |
304 | }; | |
283 | 305 | |
284 | 306 | var loadCustomFields = function () { |
285 | 307 | var deferred = $q.defer(); |
404 | 426 | headerCellTemplate: header, |
405 | 427 | sort: getColumnSort('path'), |
406 | 428 | visible: $scope.columns["path"], |
429 | }); | |
430 | $scope.gridOptions.columnDefs.push({ name : 'status_code', | |
431 | cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/defaultcolumn.html', | |
432 | headerCellTemplate: header, | |
433 | sort: getColumnSort('status_code'), | |
434 | visible: $scope.columns["status_code"], | |
407 | 435 | }); |
408 | 436 | $scope.gridOptions.columnDefs.push({ name : 'request', |
409 | 437 | cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/resolutioncolumn.html', |
579 | 607 | }); |
580 | 608 | |
581 | 609 | return response.filter(function(x){ |
582 | return !angular.equals(x["exploitdb"], []) && !angular.equals(x["metasploit"], []) | |
610 | return !angular.equals(x["exploitdb"], []) || !angular.equals(x["metasploit"], []) | |
583 | 611 | }); |
584 | 612 | |
585 | 613 | }, function(failed) { |
818 | 846 | }, |
819 | 847 | customFields: function () { |
820 | 848 | return $scope.customFields; |
849 | }, | |
850 | workspace: function () { | |
851 | return $scope.workspaceData; | |
821 | 852 | } |
822 | 853 | } |
823 | 854 | }); |
850 | 881 | resolve: resolve |
851 | 882 | }); |
852 | 883 | modal.result.then(function(data) { |
884 | var selectedVulns = []; | |
853 | 885 | $scope.getCurrentSelection().forEach(function(vuln) { |
854 | 886 | obj = {}; |
855 | 887 | obj[property] = data; |
856 | ||
888 | selectedVulns.push(vuln._id); | |
857 | 889 | if (opts.callback != undefined){ |
858 | 890 | obj = opts.callback(vuln, data); |
859 | 891 | } |
865 | 897 | console.log("Error updating vuln " + vuln._id + ": " + errorMsg); |
866 | 898 | }); |
867 | 899 | }); |
900 | ||
901 | // Storage in cookies | |
902 | $cookies.putObject("selectedVulns", selectedVulns); | |
868 | 903 | }); |
869 | 904 | } |
870 | 905 |
314 | 314 | </div><!-- .form-group --> |
315 | 315 | </div> |
316 | 316 | |
317 | <div class="col-md-4"> | |
317 | <div class="col-md-3"> | |
318 | 318 | <div class="tab-pane-header">Path</div> |
319 | 319 | <div class="form-group"> |
320 | 320 | <label class="sr-only control-label" for="vuln-path">Path</label> |
323 | 323 | </div><!-- .form-group --> |
324 | 324 | </div> |
325 | 325 | |
326 | <div class="col-md-4"> | |
326 | <div class="col-md-2"> | |
327 | <div class="tab-pane-header">Status code</div> | |
328 | <div class="form-group"> | |
329 | <label class="sr-only control-label" for="vuln-path">Status code</label> | |
330 | <input type="text" class="form-control" id="vuln-status-code" placeholder="Status code" | |
331 | ng-model="modal.data.status_code"/> | |
332 | </div><!-- .form-group --> | |
333 | </div> | |
334 | ||
335 | <div class="col-md-3"> | |
327 | 336 | <div class="tab-pane-header">Query</div> |
328 | 337 | <div class="form-group"> |
329 | 338 | <label class="sr-only control-label" for="vuln-query">Query</label> |
379 | 388 | </div> |
380 | 389 | |
381 | 390 | <div id="tab-custom-fields" class="container tab-pane-container tab-pane fade"><br> |
391 | <div ng-if="modal.customFields.length === 0" class="no-info-overlay" style="margin-bottom: 15px;"> | |
392 | <p class="no-info-text"> | |
393 | No custom fields were found. To create one refer to our | |
394 | <a href="https://github.com/infobyte/faraday/wiki/Custom-Fields" target="_blank">wiki page</a>. | |
395 | </p> | |
396 | </div> | |
382 | 397 | <div class="col-md-12 margin-bottom-15px"> |
383 | 398 | <div class="col-md-12" ng-repeat="cf in modal.customFields | orderBy : 'field_order'"> |
384 | 399 | <custom-field field="{{cf}}"></custom-field> |
391 | 406 | |
392 | 407 | <div class="modal-footer"> |
393 | 408 | <div class="modal-button"> |
394 | <button class="btn btn-success" ng-click="modal.ok()" ng-disabled="formEdit.$invalid">OK</button> | |
409 | <button class="btn btn-success" ng-click="modal.ok()" ng-disabled="formEdit.$invalid || modal.workspace.readonly == true">OK</button> | |
395 | 410 | <button class="btn btn-danger" ng-click="modal.cancel()">Cancel</button> |
396 | 411 | </div> |
397 | 412 | </div> |
87 | 87 | </label> |
88 | 88 | <label class="radio-container" |
89 | 89 | ng-click="modal.data.type = 'VulnerabilityWeb'" |
90 | title="Change type to Vulnerability Web"> | |
91 | <h5>Vulnerability Web</h5> | |
90 | title="Change type to Web Vulnerability"> | |
91 | <h5>Web Vulnerability</h5> | |
92 | 92 | <input type="radio" name="radio" ng-checked="modal.data.type === 'VulnerabilityWeb'"> |
93 | 93 | <span class="checkmark"></span> |
94 | 94 | </label> |
395 | 395 | </div><!-- .form-group --> |
396 | 396 | </div> |
397 | 397 | |
398 | <div class="col-md-4"> | |
398 | <div class="col-md-3"> | |
399 | 399 | <div class="tab-pane-header">Path</div> |
400 | 400 | <div class="form-group"> |
401 | 401 | <label class="sr-only control-label" for="vuln-path">Path</label> |
404 | 404 | </div><!-- .form-group --> |
405 | 405 | </div> |
406 | 406 | |
407 | <div class="col-md-4"> | |
407 | <div class="col-md-2"> | |
408 | <div class="tab-pane-header">Status code</div> | |
409 | <div class="form-group"> | |
410 | <label class="sr-only control-label" for="vuln-path">Status code</label> | |
411 | <input type="text" class="form-control" id="vuln-status-code" placeholder="Status code" | |
412 | ng-model="modal.data.status_code"/> | |
413 | </div><!-- .form-group --> | |
414 | </div> | |
415 | ||
416 | <div class="col-md-3"> | |
408 | 417 | <div class="tab-pane-header">Query</div> |
409 | 418 | <div class="form-group"> |
410 | 419 | <label class="sr-only control-label" for="vuln-query">Query</label> |
461 | 470 | </div> |
462 | 471 | |
463 | 472 | <div id="tab-custom-fields" class="container tab-pane-container tab-pane fade"><br> |
464 | <div class="col-md-12 margin-bottom-15px"> | |
473 | <div ng-if="modal.customFields.length === 0" class="no-info-overlay" style="margin-bottom: 15px;"> | |
474 | <p class="no-info-text"> | |
475 | No custom fields were found. To create one refer to our | |
476 | <a href="https://github.com/infobyte/faraday/wiki/Custom-Fields" target="_blank">wiki page</a>. | |
477 | </p> | |
478 | </div> | |
479 | <div class="col-md-12 margin-bottom-15px"> | |
465 | 480 | <div class="col-md-12" ng-repeat="cf in modal.customFields | orderBy : 'field_order'"> |
466 | 481 | <custom-field field="{{cf}}"></custom-field> |
467 | 482 | </div> |
468 | </div> | |
483 | </div> | |
469 | 484 | </div> |
470 | 485 | </div> |
471 | 486 |
8 | 8 | <div id="reports-main" class="fila clearfix"> |
9 | 9 | <div class="button-control col-md-12 col-sm-12 col-xs-12"> |
10 | 10 | <div class="control-wrapper control-new"> |
11 | <button type="button" class="btn btn-success btn-new" title="{{workspaceData.active == false ? 'Read-only. Workspace disabled': 'New Vulns'}}" ng-click="new()" ng-disabled="workspaceData.active == false"> | |
11 | <button type="button" class="btn btn-success btn-new" title="{{workspaceData.readonly == true ? 'Read-only. Workspace disabled': 'New Vulns'}}" ng-click="new()" ng-disabled="workspaceData.readonly == true"> | |
12 | 12 | New |
13 | 13 | </button> |
14 | 14 | </div> |
15 | 15 | <div class="control-wrapper control-edit"> |
16 | <button type="button" class="btn btn-default edit" title="{{workspaceData.active == false ? 'Read-only. Workspace disabled': 'Edit selected vulns'}}" ng-click="edit()" ng-disabled="getCurrentSelection().length != 1 || workspaceData.active == false"> | |
16 | <button type="button" class="btn btn-default edit" title="{{workspaceData.readonly == true ? 'Read-only. Workspace disabled': 'Edit selected vulns'}}" ng-click="edit()" ng-disabled="getCurrentSelection().length != 1"> | |
17 | 17 | <img src="images/icon-toolbar-edit.svg" class="edit-icon" /> |
18 | 18 | </button> |
19 | <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" title="SingleActions" ng-show="getCurrentSelection().length === 1" ng-disabled="workspaceData.active == false"> | |
19 | <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" title="SingleActions" ng-show="getCurrentSelection().length === 1" ng-disabled="workspaceData.readonly == true"> | |
20 | 20 | <span class="caret"></span> |
21 | 21 | </button> |
22 | 22 | <ul class="dropdown-menu dropdown-menu-right" role="menu"> |
23 | 23 | <li><a class="ws" ng-click="saveAsModel()">Create template</a></li> |
24 | 24 | <li><a class="ws" ng-click="showExploits()">Search exploits</a></li> |
25 | 25 | </ul> |
26 | <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" title="Actions" ng-hide="getCurrentSelection().length < 2" ng-disabled="workspaceData.active == false"> | |
26 | <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" title="Actions" ng-hide="getCurrentSelection().length < 2" ng-disabled="workspaceData.readonly == true"> | |
27 | 27 | <span class="caret"></span> |
28 | 28 | </button> |
29 | 29 | <ul class="dropdown-menu dropdown-menu-right" role="menu" ng-show="getCurrentSelection().length >= 2"> |
55 | 55 | </ul> |
56 | 56 | </div> |
57 | 57 | <div class="control-wrapper"> |
58 | <button type="button" class="btn btn-default" title="{{workspaceData.active == false ? 'Read-only. Workspace disabled': 'Delete selected items'}}" ng-click="delete()" ng-disabled="workspaceData.active == false"> | |
58 | <button type="button" class="btn btn-default" title="{{workspaceData.readonly == true? 'Read-only. Workspace disabled': 'Delete selected items'}}" ng-click="delete()" ng-disabled="workspaceData.readonly == true"> | |
59 | 59 | <img src="images/icon-toolbar-delete.svg" class="delete-icon" /> |
60 | 60 | </button> |
61 | 61 | </div> |
78 | 78 | </button> |
79 | 79 | </div> |
80 | 80 | <div class="control-wrapper download-wrapper" ng-hide="fileUploadEnabled === true"> |
81 | <button ng-click="enableFileUpload()" type="button" class="btn btn-default" title="{{workspaceData.active == false ? 'Read-only. Workspace disabled': 'Upload a report'}}" ng-disabled="workspaceData.active === false"> | |
81 | <button ng-click="enableFileUpload()" type="button" class="btn btn-default" title="{{workspaceData.readonly == true ? 'Read-only. Workspace disabled': 'Upload a report'}}" ng-disabled="workspaceData.readonly == true"> | |
82 | 82 | <img src="images/icon-toolbar-upload.svg"/> |
83 | 83 | </button> |
84 | 84 | </div> |
112 | 112 | var canAddToken = false; |
113 | 113 | var withSpace = false; |
114 | 114 | |
115 | for (var i = 0; i < expression.length; i++){ | |
116 | switch (expression[i]){ | |
115 | for (var i = 0; i < expression.length; i++) { | |
116 | switch (expression[i]) { | |
117 | 117 | case ' ': |
118 | 118 | withSpace = true; |
119 | 119 | if (isOpenQuotes === true) |
139 | 139 | break; |
140 | 140 | |
141 | 141 | default: |
142 | if(expression.substr(i, 3) === 'not' && expression.charAt(i-1) === ' ' | |
143 | && expression.charAt(i+3) === ' ' && !isOpenQuotes){ | |
144 | tokens.push('not'); | |
145 | i = i + 2; | |
146 | canAddToken = true; | |
147 | } | |
148 | ||
149 | else if(expression.substr(i, 3) === 'and' && expression.charAt(i-1) === ' ' | |
150 | && expression.charAt(i+3) === ' ' && !isOpenQuotes){ | |
151 | tokens.push('and'); | |
152 | canAddToken = true; | |
153 | i = i + 2; | |
154 | } | |
155 | ||
156 | else if(expression.substr(i, 2) === 'or' && expression.charAt(i-1) === ' ' | |
157 | && expression.charAt(i+2) === ' ' && !isOpenQuotes){ | |
158 | tokens.push('or'); | |
159 | canAddToken = true; | |
160 | i++; | |
161 | }else{ | |
162 | if((!isOpenQuotes && (withSpace || tokens.length === 0)) || canAddToken){ | |
142 | if (expression.substr(i, 3) === 'not' && expression.charAt(i - 1) === ' ' | |
143 | && expression.charAt(i + 3) === ' ' && !isOpenQuotes) { | |
144 | tokens.push('not'); | |
145 | i = i + 2; | |
146 | canAddToken = true; | |
147 | } | |
148 | ||
149 | else if (expression.substr(i, 3) === 'and' && expression.charAt(i - 1) === ' ' | |
150 | && expression.charAt(i + 3) === ' ' && !isOpenQuotes) { | |
151 | tokens.push('and'); | |
152 | canAddToken = true; | |
153 | i = i + 2; | |
154 | } | |
155 | ||
156 | else if (expression.substr(i, 2) === 'or' && expression.charAt(i - 1) === ' ' | |
157 | && expression.charAt(i + 2) === ' ' && !isOpenQuotes) { | |
158 | tokens.push('or'); | |
159 | canAddToken = true; | |
160 | i++; | |
161 | } else { | |
162 | if ((!isOpenQuotes && (withSpace || tokens.length === 0)) || canAddToken) { | |
163 | 163 | tokens.push(expression[i]); |
164 | 164 | canAddToken = false; |
165 | 165 | } |
207 | 207 | } |
208 | 208 | console.log(JSON.stringify(item)); |
209 | 209 | return item; |
210 | }; | |
211 | ||
212 | ||
213 | var processName = function (name) { | |
214 | var processedName = ""; | |
215 | switch (name) { | |
216 | case 'accountability': | |
217 | case 'availability': | |
218 | case 'confidentiality': | |
219 | case 'integrity': | |
220 | processedName = 'impact_' + name; | |
221 | break; | |
222 | case 'service': | |
223 | processedName = 'service__name'; | |
224 | break; | |
225 | case 'easeofresolution': | |
226 | case 'ease_of_resolution': | |
227 | processedName = 'ease_of_resolution'; | |
228 | break; | |
229 | case 'web': | |
230 | case 'type': | |
231 | processedName = 'type'; | |
232 | break; | |
233 | case 'creator': | |
234 | processedName = 'creator_command_tool'; | |
235 | break; | |
236 | case 'policy_violations': | |
237 | case 'policyviolations': | |
238 | processedName = 'policy_violations__name'; | |
239 | break; | |
240 | case 'host_os': | |
241 | processedName = 'host__os'; | |
242 | break; | |
243 | case 'refs': | |
244 | case 'ref': | |
245 | processedName = 'references__name'; | |
246 | break; | |
247 | case 'evidence': | |
248 | processedName = 'evidence__filename'; | |
249 | break; | |
250 | case 'params': | |
251 | case 'parameters': | |
252 | processedName = 'parameters'; | |
253 | break; | |
254 | case 'pname': | |
255 | case 'parameter_name': | |
256 | processedName = 'parameter_name'; | |
257 | break; | |
258 | case 'query': | |
259 | case 'query_string': | |
260 | processedName = 'query_string'; | |
261 | break; | |
262 | case 'tags': | |
263 | case 'tag': | |
264 | processedName = 'tags__name'; | |
265 | break; | |
266 | default: | |
267 | processedName = name; | |
268 | break; | |
269 | } | |
270 | ||
271 | return processedName; | |
272 | }; | |
273 | ||
274 | var processOperator = function (name, operator) { | |
275 | var processedOperator = ""; | |
276 | switch (name) { | |
277 | case 'confirmed': | |
278 | case 'impact_accountability': | |
279 | case 'impact_availability': | |
280 | case 'impact_confidentiality': | |
281 | case 'impact_integrity': | |
282 | case 'ease_of_resolution': | |
283 | case 'type': | |
284 | case 'id': | |
285 | processedOperator = operator !== 'not' ? '==' : '!='; | |
286 | break; | |
287 | case 'severity': | |
288 | case 'target': | |
289 | processedOperator = operator !== 'not' ? 'eq' : '!='; | |
290 | break; | |
291 | case 'service__name': | |
292 | case 'host__os': | |
293 | processedOperator = operator !== 'not' ? 'has' : '!='; | |
294 | break; | |
295 | case 'policy_violations__name': | |
296 | case 'references__name': | |
297 | case 'evidence__filename': | |
298 | case 'tags__name': | |
299 | processedOperator = operator !== 'not' ? 'any' : '!='; | |
300 | break; | |
301 | default: | |
302 | processedOperator = operator !== 'not' ? 'ilike' : '!='; | |
303 | break; | |
304 | } | |
305 | ||
306 | return processedOperator; | |
210 | 307 | }; |
211 | 308 | |
212 | 309 | var processTerm = function (term, operator) { |
221 | 318 | if (array.length === 2) { |
222 | 319 | var name = array[0]; |
223 | 320 | var val = array[1].replace(/"/g, ''); |
224 | var op = 'like'; | |
225 | if (operator !== 'not') { | |
226 | if (name === 'confirmed' || name === 'accountability' || name === 'availability' || name === 'confidentiality' || name === 'integrity') { | |
227 | if (name !== 'confirmed') | |
228 | name = 'impact_' + name; | |
229 | ||
230 | op = '=='; | |
231 | } | |
232 | ||
233 | if (name === 'severity' || name === 'target'){ | |
234 | op = 'eq' | |
235 | } | |
236 | } else { | |
237 | if (name === 'accountability' || name === 'availability' || name === 'confidentiality' || name === 'integrity') { | |
238 | name = 'impact_' + name; | |
239 | } | |
240 | op = '!=' | |
241 | } | |
242 | ||
321 | var op; | |
322 | ||
323 | name = processName(name); | |
324 | op = processOperator(name, operator); | |
243 | 325 | |
244 | 326 | if (val === 'info') val = 'informational'; |
245 | 327 | if (val === 'med') val = 'medium'; |
247 | 329 | res.name = name; |
248 | 330 | res.op = op; |
249 | 331 | res.val = val; |
250 | if (op === 'like') { | |
332 | if (op === 'ilike' && name !== 'creator_command_tool') { | |
251 | 333 | res.val = '%' + val + '%'; |
252 | 334 | } |
253 | 335 | return res |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | 5 | .controller('vulndDbModalEdit', |
6 | ['$scope', '$modalInstance', 'VulnModel', 'model', 'EXPLOITATIONS', | |
7 | function($scope, $modalInstance, VulnModel, model, EXPLOITATIONS) { | |
6 | ['$scope', '$modalInstance', 'VulnModel', 'model', 'EXPLOITATIONS', 'EASEOFRESOLUTION', | |
7 | function($scope, $modalInstance, VulnModel, model, EXPLOITATIONS, EASEOFRESOLUTION) { | |
8 | 8 | |
9 | 9 | $scope.data; |
10 | 10 | $scope.openedStart; |
11 | 11 | $scope.openedEnd; |
12 | var EXCLUDED_TOKENS = [""]; | |
12 | 13 | |
13 | 14 | var init = function() { |
14 | $scope.exploitations = EXPLOITATIONS | |
15 | $scope.exploitations = EXPLOITATIONS; | |
16 | $scope.easeofresolution = EASEOFRESOLUTION; | |
15 | 17 | $scope.data = new VulnModel; |
16 | 18 | $scope.data.set(model); |
19 | $scope.impact = angular.copy($scope.data.impact); | |
20 | $scope.policyviolations = clearList(angular.copy($scope.data.policyviolations), EXCLUDED_TOKENS); | |
21 | $scope.references = clearList(angular.copy($scope.data.refs), EXCLUDED_TOKENS); | |
22 | $scope.new_policyviolation = ""; | |
23 | $scope.new_reference = ""; | |
17 | 24 | }; |
18 | 25 | |
19 | 26 | $scope.ok = function() { |
27 | $scope.data.impact = angular.copy($scope.impact); | |
28 | $scope.data.policyviolations = angular.copy($scope.policyviolations); | |
29 | $scope.data.refs = angular.copy($scope.references); | |
30 | $scope.data.references = $scope.data.refs.join(','); | |
20 | 31 | $modalInstance.close($scope.data); |
21 | 32 | }; |
22 | 33 | |
31 | 42 | $modalInstance.dismiss('cancel'); |
32 | 43 | }; |
33 | 44 | |
45 | $scope.toggleImpact = function(key) { | |
46 | $scope.impact[key] = !$scope.impact[key]; | |
47 | }; | |
48 | ||
49 | $scope.newPolicyViolation = function() { | |
50 | if ($scope.new_policyviolation !== "") { | |
51 | // we need to check if the policy violation already exists | |
52 | if ($scope.policyviolations.filter(function(policyviolation) {return policyviolation === $scope.new_policyviolation}).length === 0) { | |
53 | $scope.policyviolations.push($scope.new_policyviolation); | |
54 | $scope.new_policyviolation = ""; | |
55 | } | |
56 | } | |
57 | }; | |
58 | ||
59 | $scope.newReference = function() { | |
60 | if ($scope.new_reference !== "") { | |
61 | // we need to check if the reference already exists | |
62 | if ($scope.references.filter(function(reference) {return reference === $scope.new_reference}).length === 0) { | |
63 | $scope.references.push($scope.new_reference); | |
64 | $scope.new_reference = ""; | |
65 | } | |
66 | } | |
67 | }; | |
68 | ||
69 | var clearList = function (list, excludedTokens) { | |
70 | for (var i = 0; i< list.length ; i++){ | |
71 | if (excludedTokens.indexOf(list[i]) > -1){ | |
72 | list.splice(i, 1); | |
73 | } | |
74 | } | |
75 | return list; | |
76 | }; | |
77 | ||
34 | 78 | init(); |
35 | 79 | }]); |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | 5 | .controller('vulnModelModalNew', |
6 | ['$scope', '$modalInstance', 'VulnModel', 'vulnModelsManager', 'EXPLOITATIONS', | |
7 | function($scope, $modalInstance, VulnModel, vulnModelsManager, EXPLOITATIONS) { | |
6 | ['$scope', '$modalInstance', 'VulnModel', 'vulnModelsManager', 'EXPLOITATIONS', 'EASEOFRESOLUTION', | |
7 | function($scope, $modalInstance, VulnModel, vulnModelsManager, EXPLOITATIONS, EASEOFRESOLUTION) { | |
8 | 8 | |
9 | 9 | $scope.data; |
10 | 10 | $scope.exploitations; |
12 | 12 | |
13 | 13 | var init = function() { |
14 | 14 | $scope.exploitations = EXPLOITATIONS; |
15 | $scope.easeofresolution = EASEOFRESOLUTION; | |
15 | 16 | $scope.data = new VulnModel; |
16 | 17 | $scope.models = vulnModelsManager.models; |
17 | 18 | // $scope.exploitations = ['a']; |
39 | 40 | $scope.data.model = $scope.other_model; |
40 | 41 | } |
41 | 42 | |
43 | if ($scope.data.easeofresolution === ""){ | |
44 | $scope.data.easeofresolution = null; | |
45 | } | |
46 | ||
42 | 47 | $modalInstance.close($scope.data); |
43 | 48 | }; |
44 | 49 | |
46 | 51 | $modalInstance.dismiss('cancel'); |
47 | 52 | }; |
48 | 53 | |
54 | $scope.toggleImpact = function(key) { | |
55 | $scope.data.impact[key] = !$scope.data.impact[key]; | |
56 | }; | |
57 | ||
58 | $scope.newPolicyViolation = function() { | |
59 | if ($scope.new_policyviolation !== "") { | |
60 | // we need to check if the policy violation already exists | |
61 | if ($scope.data.policyviolations.filter(function(policyviolation) {return policyviolation === $scope.new_policyviolation}).length === 0) { | |
62 | $scope.data.policyviolations.push($scope.new_policyviolation); | |
63 | $scope.new_policyviolation = ""; | |
64 | } | |
65 | } | |
66 | }; | |
67 | ||
68 | $scope.newReference = function() { | |
69 | if ($scope.new_reference !== "") { | |
70 | // we need to check if the reference already exists | |
71 | if ($scope.data.references.filter(function(reference) {return reference === $scope.new_reference}).length === 0) { | |
72 | $scope.data.references.push($scope.new_reference); | |
73 | $scope.new_reference = ""; | |
74 | } | |
75 | } | |
76 | }; | |
77 | ||
49 | 78 | init(); |
50 | 79 | }]); |
126 | 126 | var counter = 0; |
127 | 127 | $scope.loading = true; |
128 | 128 | datas.forEach(function(data) { |
129 | if (!data.easeofresolution || data.easeofresolution === "") | |
130 | data.easeofresolution = null; | |
129 | 131 | $scope.insert(data).then(function() { |
130 | 132 | counter = counter + 1; |
131 | 133 | if (length == counter) { |
30 | 30 | </div><!-- .form-group --> |
31 | 31 | <div class="form-group"> |
32 | 32 | <div class="col-md-12"> |
33 | <label for="data">Data</label> | |
34 | <textarea class="form-control" id="data" placeholder="Data" ng-model="data.data"></textarea> | |
35 | </div> | |
36 | </div><!-- .form-group --> | |
37 | <div class="form-group"> | |
38 | <div class="col-md-12"> | |
33 | 39 | <label for="resolution">Resolution</label> |
34 | 40 | <textarea class="form-control" name="resolution" placeholder="Resolution" ng-model="data.resolution"></textarea> |
35 | 41 | </div> |
36 | 42 | </div><!-- .form-group --> |
37 | 43 | <div class="form-group"> |
38 | 44 | <div class="col-md-12"> |
39 | <label for="References">References</label> | |
40 | <textarea class="form-control" name="References" placeholder="References" ng-model="data.references"></textarea> | |
45 | <label for="references">References</label> | |
46 | <div class="input-group margin-bottom-sm"> | |
47 | <label class="sr-only" for="references">References</label> | |
48 | <input type="text" class="form-control" id="references" placeholder="References" ng-model="new_reference"/> | |
49 | <span class="input-group-addon cursor" ng-click="newReference()"><i class="fa fa-plus-circle"></i></span> | |
50 | </div> | |
51 | </div> | |
52 | <div class="col-md-12 reference" ng-repeat="reference in references"> | |
53 | <div class="input-group margin-bottom-sm"> | |
54 | <label class="sr-only" for="ireferences">References</label> | |
55 | <input type="text" class="form-control" id="ireferences" placeholder="References" ng-model="reference" disabled/> | |
56 | <span class="input-group-addon cursor" ng-click="references.splice($index, 1)"><i class="fa fa-minus-circle"></i></span> | |
57 | </div> | |
41 | 58 | </div> |
42 | 59 | </div><!-- .form-group --> |
60 | ||
61 | <div class="form-group"> | |
62 | <div class="col-md-12"> | |
63 | <label for="vuln-policyviolations">Policy Violations</label> | |
64 | <div class="input-group margin-bottom-sm"> | |
65 | <label class="sr-only" for="vuln-policyviolations">Policy Violations</label> | |
66 | <input type="text" class="form-control" id="vuln-policyviolations" placeholder="Policy Violations" ng-model="new_policyviolation"/> | |
67 | <span class="input-group-addon cursor" ng-click="newPolicyViolation()"><i class="fa fa-plus-circle"></i></span> | |
68 | </div> | |
69 | </div> | |
70 | <div class="col-md-12 reference" ng-repeat="policyviolation in policyviolations"> | |
71 | <div class="input-group margin-bottom-sm"> | |
72 | <label class="sr-only" for="vuln-policyviolations">Policy Violation</label> | |
73 | <input type="text" class="form-control" id="vuln-policyviolations" placeholder="Policy Violation" ng-model="policyviolation" disabled/> | |
74 | <span class="input-group-addon cursor" ng-click="policyviolations.splice($index, 1)"><i class="fa fa-minus-circle"></i></span> | |
75 | </div> | |
76 | </div> | |
77 | </div><!-- .form-group --> | |
78 | ||
79 | <div class="form-group"> | |
80 | <div class="col-md-6"> | |
81 | <label>Impact</label> | |
82 | <h4><span ng-repeat="(key, value) in impact" class="normal-size" style="cursor: pointer;"> | |
83 | <span ng-class="{'label-impact label-default-impact': !value, 'label-impact label-success-impact': value}" ng-click="toggleImpact(key)">{{key}}</span> | |
84 | </span></h4><!-- .normal-size --> | |
85 | </div> | |
86 | <div class="col-md-2"></div> | |
87 | <div class="col-md-4"> | |
88 | <label>Ease of Resolution</label> | |
89 | <select class="form-control" ng-model="data.easeofresolution" ng-options="e for e in easeofresolution" style="padding:0px 12px;"> | |
90 | <option value=""></option> | |
91 | </select> | |
92 | </div> | |
93 | </div><!-- .form-group --> | |
94 | ||
43 | 95 | <div class="form-group"> |
44 | 96 | <div class="col-md-12"> |
45 | 97 | <label for="Explotation">Severity</label> |
30 | 30 | </div><!-- .form-group --> |
31 | 31 | <div class="form-group"> |
32 | 32 | <div class="col-md-12"> |
33 | <label for="data">Data</label> | |
34 | <textarea class="form-control" id="data" placeholder="Data" ng-model="data.data"></textarea> | |
35 | </div> | |
36 | </div><!-- .form-group --> | |
37 | <div class="form-group"> | |
38 | <div class="col-md-12"> | |
33 | 39 | <label for="resolution">Resolution</label> |
34 | 40 | <textarea class="form-control" name="resolution" placeholder="Resolution" ng-model="data.resolution"></textarea> |
35 | 41 | </div> |
36 | 42 | </div><!-- .form-group --> |
43 | ||
37 | 44 | <div class="form-group"> |
38 | 45 | <div class="col-md-12"> |
39 | <label for="References">References</label> | |
40 | <textarea class="form-control" name="References" placeholder="References" ng-model="data.references"></textarea> | |
46 | <label for="references">References</label> | |
47 | <div class="input-group margin-bottom-sm"> | |
48 | <label class="sr-only" for="references">References</label> | |
49 | <input type="text" class="form-control" id="references" placeholder="References" ng-model="new_reference"/> | |
50 | <span class="input-group-addon cursor" ng-click="newReference()"><i class="fa fa-plus-circle"></i></span> | |
51 | </div> | |
52 | </div> | |
53 | <div class="col-md-12 reference" ng-repeat="reference in data.references"> | |
54 | <div class="input-group margin-bottom-sm"> | |
55 | <label class="sr-only" for="ireferences">References</label> | |
56 | <input type="text" class="form-control" id="ireferences" placeholder="References" ng-model="reference" disabled/> | |
57 | <span class="input-group-addon cursor" ng-click="data.references.splice($index, 1)"><i class="fa fa-minus-circle"></i></span> | |
58 | </div> | |
41 | 59 | </div> |
42 | 60 | </div><!-- .form-group --> |
61 | ||
62 | ||
63 | <div class="form-group"> | |
64 | <div class="col-md-12"> | |
65 | <label for="vuln-policyviolations">Policy Violations</label> | |
66 | <div class="input-group margin-bottom-sm"> | |
67 | <label class="sr-only" for="vuln-policyviolations">Policy Violations</label> | |
68 | <input type="text" class="form-control" id="vuln-policyviolations" placeholder="Policy Violations" ng-model="new_policyviolation"/> | |
69 | <span class="input-group-addon cursor" ng-click="newPolicyViolation()"><i class="fa fa-plus-circle"></i></span> | |
70 | </div> | |
71 | </div> | |
72 | <div class="col-md-12 reference" ng-repeat="policyviolation in data.policyviolations"> | |
73 | <div class="input-group margin-bottom-sm"> | |
74 | <label class="sr-only" for="vuln-policyviolations">Policy Violation</label> | |
75 | <input type="text" class="form-control" id="vuln-policyviolations" placeholder="Policy Violation" ng-model="policyviolation" disabled/> | |
76 | <span class="input-group-addon cursor" ng-click="data.policyviolations.splice($index, 1)"><i class="fa fa-minus-circle"></i></span> | |
77 | </div> | |
78 | </div> | |
79 | </div><!-- .form-group --> | |
80 | ||
81 | <div class="form-group"> | |
82 | <div class="col-md-6"> | |
83 | <label>Impact</label> | |
84 | <h4><span ng-repeat="(key, value) in data.impact" class="normal-size" style="cursor: pointer;"> | |
85 | <span ng-class="{'label-impact label-default-impact': !value, 'label-impact label-success-impact': value}" ng-click="toggleImpact(key)">{{key}}</span> | |
86 | </span></h4><!-- .normal-size --> | |
87 | </div> | |
88 | <div class="col-md-2"></div> | |
89 | <div class="col-md-4"> | |
90 | <label>Ease of Resolution</label> | |
91 | <select class="form-control" ng-model="data.easeofresolution" ng-options="e for e in easeofresolution" style="padding:0px 12px;"> | |
92 | <option value=""></option> | |
93 | </select> | |
94 | </div> | |
95 | </div><!-- .form-group --> | |
96 | ||
43 | 97 | <div class="form-group"> |
44 | 98 | <div class="col-md-12"> |
45 | 99 | <label for="Explotation">Severity *</label> |
9 | 9 | <div class="reports col-md-12 col-sm-12 col-xs-12"> |
10 | 10 | <div class="button-control justify-flex-start col-md-12 col-sm-12 col-xs-12"> |
11 | 11 | <div class="control-wrapper control-new"> |
12 | <button id="import" type="button" class="btn btn-success btn-new" title="New host" ng-click="disabledClick || new()"> | |
12 | <button id="import" type="button" class="btn btn-success btn-new" title="New vuln template" ng-click="disabledClick || new()"> | |
13 | 13 | New |
14 | 14 | </button> |
15 | 15 | </div> |
56 | 56 | <th class="ui-grid-cell-contents ui-grid-header-cell"> |
57 | 57 | <span ng-click="toggleSort('description')">Description</span> |
58 | 58 | </th> |
59 | <th class="ui-grid-cell-contents ui-grid-header-cell"> | |
60 | <span ng-click="toggleSort('references')">References</span> | |
61 | </th> | |
62 | <th class="ui-grid-cell-contents ui-grid-header-cell"> | |
63 | <span ng-click="toggleSort('data')">Data</span> | |
64 | </th> | |
59 | 65 | <th class="ui-grid-cell-contents ui-grid-header-cell"> |
60 | 66 | <span ng-click="toggleSort('resolution')">Resolution</span> |
61 | 67 | </th> |
62 | 68 | <th class="ui-grid-cell-contents ui-grid-header-cell"> |
69 | <span ng-click="toggleSort('impact')">Impact</span> | |
70 | </th> | |
71 | <th class="ui-grid-cell-contents ui-grid-header-cell"> | |
63 | 72 | <span ng-click="toggleSort('exploitation')">Severity</span> |
73 | </th> | |
74 | <th class="ui-grid-cell-contents ui-grid-header-cell"> | |
75 | <span ng-click="toggleSort('easeofresolution')">Ease of Resolution</span> | |
76 | </th> | |
77 | <th class="ui-grid-cell-contents ui-grid-header-cell"> | |
78 | <span ng-click="toggleSort('policyviolations')">Policy Violations</span> | |
64 | 79 | </th> |
65 | 80 | </tr> |
66 | 81 | </thead> |
73 | 88 | <td class="checkbox-select"><input type="checkbox" name="{{model._id}}"/></td> |
74 | 89 | <td>{{model.name}}</td> |
75 | 90 | <td>{{model.description}}</td> |
91 | <td><div ng-repeat="ref in model.refs">{{ref}}</div></td> | |
92 | <td>{{model.data}}</td> | |
76 | 93 | <td>{{model.resolution}}</td> |
94 | <td><div ng-repeat="(key, value) in model.impact" ng-if="value == true">{{key}}</div></td> | |
77 | 95 | <td>{{model.exploitation}}</td> |
96 | <td>{{model.easeofresolution}}</td> | |
97 | <td><div ng-repeat="policy in model.policyviolations">{{policy}}</div></td> | |
78 | 98 | </tr> |
79 | 99 | </tbody> |
80 | 100 | </table> |
11 | 11 | this.cwe = ""; |
12 | 12 | this.description = ""; |
13 | 13 | this.desc = ""; |
14 | this.data = ""; | |
14 | 15 | this.exploitation = ""; |
15 | 16 | this.name = ""; |
16 | 17 | this.references = []; |
17 | 18 | this.refs = []; |
19 | this.data = ""; | |
18 | 20 | this.resolution = ""; |
21 | this.impact = { | |
22 | accountability: false, | |
23 | availability: false, | |
24 | confidentiality: false, | |
25 | integrity: false | |
26 | }; | |
27 | this.policyviolations = []; | |
28 | this.easeofresolution = ""; | |
19 | 29 | if (data) { |
20 | 30 | if(data.name === undefined || data.name === "") { |
21 | 31 | throw new Error("Unable to create a Vulnerability Model whithout a name"); |
27 | 37 | VulnModel.prototype = { |
28 | 38 | |
29 | 39 | public_properties: ['exploitation', 'references', 'name', 'resolution', 'cwe', 'description', |
30 | 'desc', 'id', 'refs'], | |
40 | 'desc', 'data', 'id', 'refs', 'impact', 'easeofresolution', 'policyviolations'], | |
31 | 41 | |
32 | 42 | set: function(data) { |
33 | 43 | var self = this; |
15 | 15 | |
16 | 16 | var public_properties = [ |
17 | 17 | 'method', 'params', 'path', 'pname', 'query', |
18 | 'request', 'response', 'website' | |
18 | 'request', 'response', 'website', 'status_code' | |
19 | 19 | ]; |
20 | 20 | |
21 | 21 | WebVuln.prototype = Object.create(Vuln.prototype); |
372 | 372 | $scope.search = ''; |
373 | 373 | }; |
374 | 374 | |
375 | $scope.readonlyToggle = function (ws) { | |
376 | workspacesFact.readOnlyToogle(ws.name).then(function (resp) { | |
377 | ws.readonly = resp.data; | |
378 | }); | |
379 | }; | |
380 | ||
375 | 381 | $scope.init(); |
376 | 382 | }]); |
43 | 43 | <table class="table-v3 table table-responsive"> |
44 | 44 | <thead> |
45 | 45 | <tr class="ui-grid-header"> |
46 | <th class="ui-grid-cell-contents ui-grid-header-cell">Read only</th> | |
46 | 47 | <th class="ui-grid-cell-contents ui-grid-header-cell">Active</th> |
47 | 48 | <th class="ui-grid-cell-contents ui-grid-header-cell"> |
48 | 49 | <a href="" ng-click="toggleSort('name')">Name</a> |
60 | 61 | selection-model selection-model-selected-class="multi-selected"> |
61 | 62 | |
62 | 63 | <td class="ui-grid-cell-contents active-toggle"> |
63 | <span ng-click="activeToggle(ws)" class="active-toggle-container" ng-class="{ disabled:ws.active === false }" uib-tooltip="{{(ws.active === true) ? 'Disable workspace' : 'Enable workspace'}}" tooltip-placement="right"> | |
64 | <img ng-src="{{ (ws.active) ? 'images/icon-list-confirmed.svg' : 'images/icon-list-notconfirmed.svg'}}" class="confirm-icon" ng-style="{ 'opacity': (ws.active === true) ? '1' : '0.7' }" /> | |
65 | </span> | |
64 | <div class="toogle-img-container"> | |
65 | <span ng-click="readonlyToggle(ws)" class="active-toggle-container" ng-class="{ disabled:ws.readonly === false }" uib-tooltip="{{(ws.readonly === false) ? 'Workspace with all permissions' : 'Workspace read only'}}" tooltip-placement="right"> | |
66 | <img ng-src="{{ (ws.readonly == true) ? 'images/icon-list-confirmed.svg' : 'images/icon-list-notconfirmed.svg'}}" class="confirm-icon" ng-style="{ 'opacity': (ws.readonly === true) ? '1' : '0.7' }" /> | |
67 | </span> | |
68 | </div> | |
69 | </td> | |
70 | ||
71 | <td class="ui-grid-cell-contents active-toggle"> | |
72 | <div class="toogle-img-container"> | |
73 | <span ng-click="activeToggle(ws)" class="active-toggle-container" ng-class="{ disabled:ws.active === false }" uib-tooltip="{{(ws.active === true) ? 'Disable workspace' : 'Enable workspace'}}" tooltip-placement="right"> | |
74 | <img ng-src="{{ (ws.active) ? 'images/icon-list-confirmed.svg' : 'images/icon-list-notconfirmed.svg'}}" class="confirm-icon" ng-style="{ 'opacity': (ws.active === true) ? '1' : '0.7' }" /> | |
75 | </span> | |
76 | </div> | |
66 | 77 | </td> |
67 | 78 | <td class="ui-grid-cell-contents"> |
68 | 79 | <span class="onhover upsize" ng-click="dashboardRedirect(ws.name)"> |
127 | 127 | return deferred.promise; |
128 | 128 | }; |
129 | 129 | |
130 | workspacesFact.readOnlyToogle = function(wsName) { | |
131 | var deferred = $q.defer(); | |
132 | ServerAPI.readOnlyToogle(wsName).then(function(data){ | |
133 | deferred.resolve(data); | |
134 | }, function(err){ | |
135 | deferred.reject(err); | |
136 | }); | |
137 | return deferred.promise; | |
138 | }; | |
139 | ||
130 | 140 | return workspacesFact; |
131 | 141 | }]); |
2 | 2 | in |
3 | 3 | mkShell { |
4 | 4 | buildInputs = with python27Packages; |
5 | [virtualenv pyopenssl psycopg2 pillow pygobject3 | |
5 | [virtualenv pyopenssl psycopg2 pillow pygobject3 pynacl matplotlib lxml ldap | |
6 | 6 | gobjectIntrospection gtk3 gnome3.vte ipython |
7 | 7 | ]; |
8 | 8 | shellHook = '' |
8 | 8 | import os |
9 | 9 | import sys |
10 | 10 | import json |
11 | import random | |
12 | import string | |
13 | 11 | import inspect |
14 | ||
15 | 12 | import pytest |
16 | import psycopg2 | |
17 | from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT | |
18 | ||
19 | 13 | from factory import Factory |
20 | 14 | from flask.testing import FlaskClient |
21 | 15 | from flask_principal import Identity, identity_changed |
26 | 20 | from server.app import create_app |
27 | 21 | from server.models import db |
28 | 22 | from test_cases import factories |
29 | from server import config | |
30 | ||
23 | ||
24 | ||
25 | TEMPORATY_SQLITE = NamedTemporaryFile() | |
31 | 26 | # Discover factories to automatically register them to pytest-factoryboy and to |
32 | 27 | # override its session |
33 | 28 | enabled_factories = [] |
75 | 70 | def pytest_addoption(parser): |
76 | 71 | # currently for tests using sqlite and memory have problem while using transactions |
77 | 72 | # we need to review sqlite configuraitons for persistence using PRAGMA. |
78 | parser.addoption('--use-postgresql', action='store_true', | |
79 | help="Forces the tests to be executed in postgresql " | |
80 | "using server.ini credentials") | |
81 | parser.addoption('--connection-string', | |
73 | parser.addoption('--connection-string', default='sqlite:////{0}'.format(TEMPORATY_SQLITE.name), | |
82 | 74 | help="Database connection string. Defaults to in-memory " |
83 | 75 | "sqlite if not specified:") |
84 | 76 | parser.addoption('--ignore-nplusone', action='store_true', |
93 | 85 | config.option.markexpr = 'not hypothesis' |
94 | 86 | |
95 | 87 | |
96 | @pytest.fixture(scope='function') | |
88 | @pytest.fixture(scope='session') | |
97 | 89 | def app(request): |
98 | connection_string = request.config.getoption( | |
99 | '--connection-string') | |
100 | use_postgresql = request.config.getoption( | |
101 | '--use-postgresql') | |
102 | sqlite = False | |
103 | postgres_user, postgres_password = None, None | |
104 | ||
105 | if use_postgresql and not connection_string: | |
106 | connection_string = config.database.connection_string | |
107 | if connection_string: | |
108 | postgres_user, postgres_password = connection_string.split('://')[1].split('@')[0].split(':') | |
109 | ||
110 | if postgres_user and postgres_password: | |
111 | host = connection_string.split('://')[1].split('@')[1].split('/')[0] | |
112 | con = psycopg2.connect(dbname='postgres', | |
113 | user=postgres_user, | |
114 | host=host, | |
115 | password=postgres_password) | |
116 | ||
117 | con.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) | |
118 | cur = con.cursor() | |
119 | db_name = ''.join(random.SystemRandom().choice(string.ascii_uppercase) for _ in range(20)) | |
120 | cur.execute("CREATE DATABASE \"%s\" ;" % db_name) | |
121 | connection_string = 'postgresql+psycopg2://{postgres_user}:{postgres_password}@{host}/{db_name}'.format( | |
122 | postgres_user=postgres_user, | |
123 | postgres_password=postgres_password, | |
124 | host=host, | |
125 | db_name=db_name, | |
126 | ) | |
127 | con.close() | |
128 | else: | |
129 | sqlite = True | |
130 | connection_string = 'sqlite:///' | |
131 | ||
132 | app = create_app(db_connection_string=connection_string, testing=True) | |
90 | app = create_app(db_connection_string=request.config.getoption( | |
91 | '--connection-string'), testing=True) | |
133 | 92 | app.test_client_class = CustomClient |
134 | 93 | |
135 | 94 | # Establish an application context before running the tests. |
137 | 96 | ctx.push() |
138 | 97 | |
139 | 98 | def teardown(): |
140 | with ctx: | |
141 | db.session.close() | |
142 | db.engine.dispose() | |
99 | TEMPORATY_SQLITE.close() | |
143 | 100 | ctx.pop() |
144 | if not sqlite: | |
145 | postgres_user, postgres_password = connection_string.split('://')[1].split('@')[0].split(':') | |
146 | host = connection_string.split('://')[1].split('@')[1].split('/')[0] | |
147 | con = psycopg2.connect(dbname='postgres', | |
148 | user=postgres_user, | |
149 | host=host, | |
150 | password=postgres_password) | |
151 | ||
152 | con.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) | |
153 | cur = con.cursor() | |
154 | cur.execute("DROP DATABASE \"%s\" ;" % db_name) | |
155 | 101 | |
156 | 102 | request.addfinalizer(teardown) |
157 | 103 | app.config['NPLUSONE_RAISE'] = not request.config.getoption( |
159 | 105 | return app |
160 | 106 | |
161 | 107 | |
162 | @pytest.fixture(scope='function') | |
108 | @pytest.fixture(scope='session') | |
163 | 109 | def database(app, request): |
164 | 110 | """Session-wide test database.""" |
111 | ||
112 | def teardown(): | |
113 | try: | |
114 | db.engine.execute('DROP TABLE vulnerability CASCADE') | |
115 | except Exception: | |
116 | pass | |
117 | try: | |
118 | db.engine.execute('DROP TABLE vulnerability_template CASCADE') | |
119 | except Exception: | |
120 | pass | |
121 | db.drop_all() | |
165 | 122 | |
166 | 123 | # Disable check_vulnerability_host_service_source_code constraint because |
167 | 124 | # it doesn't work in sqlite |
168 | 125 | vuln_constraints = db.metadata.tables['vulnerability'].constraints |
169 | try: | |
170 | vuln_constraints.remove(next( | |
171 | constraint for constraint in vuln_constraints | |
172 | if constraint.name == 'check_vulnerability_host_service_source_code')) | |
173 | except StopIteration: | |
174 | pass | |
175 | db.init_app(app) | |
126 | vuln_constraints.remove(next( | |
127 | constraint for constraint in vuln_constraints | |
128 | if constraint.name == 'check_vulnerability_host_service_source_code')) | |
129 | ||
130 | db.app = app | |
176 | 131 | db.create_all() |
177 | 132 | |
133 | request.addfinalizer(teardown) | |
178 | 134 | return db |
179 | 135 | |
180 | 136 | |
214 | 170 | for further information |
215 | 171 | """ |
216 | 172 | connection = database.engine.connect() |
173 | transaction = connection.begin() | |
217 | 174 | |
218 | 175 | options = {"bind": connection, 'binds': {}} |
219 | 176 | session = db.create_scoped_session(options=options) |
220 | 177 | |
221 | 178 | # start the session in a SAVEPOINT... |
222 | #session.begin_nested() | |
179 | session.begin_nested() | |
180 | ||
181 | # then each time that SAVEPOINT ends, reopen it | |
182 | @event.listens_for(session, "after_transaction_end") | |
183 | def restart_savepoint(session, transaction): | |
184 | if transaction.nested and not transaction._parent.nested: | |
185 | ||
186 | # ensure that state is expired the way | |
187 | # session.commit() at the top level normally does | |
188 | # (optional step) | |
189 | session.expire_all() | |
190 | ||
191 | session.begin_nested() | |
223 | 192 | |
224 | 193 | database.session = session |
225 | 194 | db.session = session |
232 | 201 | # Session above (including calls to commit()) |
233 | 202 | # is rolled back. |
234 | 203 | # be careful with this!!!!! |
204 | transaction.rollback() | |
235 | 205 | connection.close() |
236 | 206 | session.remove() |
237 | 207 | |
241 | 211 | |
242 | 212 | @pytest.fixture |
243 | 213 | def test_client(app): |
214 | ||
215 | # flask.g is persisted in requests, and the werkzeug | |
216 | # CSRF checker could fail if we don't do this | |
217 | from flask import g | |
218 | try: | |
219 | del g.csrf_token | |
220 | except: | |
221 | pass | |
222 | ||
244 | 223 | return app.test_client() |
245 | 224 | |
246 | 225 | |
296 | 275 | app.config['NPLUSONE_RAISE'] = False |
297 | 276 | yield |
298 | 277 | app.config['NPLUSONE_RAISE'] = old |
278 | ||
279 | ||
280 | @pytest.fixture | |
281 | def csrf_token(logged_user, test_client): | |
282 | session_response = test_client.get('/session') | |
283 | return session_response.json.get('csrf_token') |
0 | # This is an ubuntu with system packages required to run faraday | |
1 | # It doesn't install the python dependencies. That is done in | |
2 | # the gitlab CI job to avoid having old versions of packages. | |
3 | # This is used to build registry.gitlab.com/faradaysec/faraday/faraday_testing_base | |
4 | FROM ubuntu | |
5 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime | |
6 | RUN echo $TZ > /etc/timezone | |
7 | RUN apt-get update -qy | |
8 | RUN apt-get -y install build-essential ipython python-setuptools python-pip python-dev pkg-config libssl-dev libffi-dev libxml2-dev libxslt1-dev libfreetype6-dev libpng-dev postgresql sudo libsasl2-dev libldap2-dev git | |
9 | RUN apt-get install -y python-dev python-pip |
0 | [faraday_server] | |
1 | port = 5985 | |
2 | bind_address = localhost | |
3 | websocket_port = 9000 | |
4 | secret_key = 2nQe7hpNAUPfEb5xSal8K41s3 | |
5 | ||
6 | [ssl] | |
7 | port = 6985 | |
8 | certificate = | |
9 | keyfile = | |
10 | ||
11 | [couchdb] | |
12 | host = localhost | |
13 | port = 5984 | |
14 | ssl_port = 6984 | |
15 | user = | |
16 | password = | |
17 | protocol = http | |
18 | ||
19 | [storage] | |
20 | path = /home/lcubo/.faraday/storage | |
21 |
11 | 11 | class TestAPIInfoEndpoint: |
12 | 12 | |
13 | 13 | def test_api_info(self, test_client): |
14 | current_dir = os.path.dirname(os.path.realpath(__file__)) | |
14 | current_dir = os.getcwd() | |
15 | 15 | # this is a bug on the info api! |
16 | 16 | # we require faraday to be a package since we can't import |
17 | 17 | # from base path when our current working dir is test_cases. |
0 | ||
1 | from test_cases import factories | |
2 | ||
3 | class TestLogin(): | |
4 | def test_case_bug_with_username(self, test_client, session): | |
5 | """ | |
6 | When the user case does not match the one in database, | |
7 | the form is valid but no record was found in the database. | |
8 | """ | |
9 | ||
10 | susan = factories.UserFactory.create( | |
11 | active=True, | |
12 | username='Susan', | |
13 | password='pepito', | |
14 | role='pentester') | |
15 | session.add(susan) | |
16 | session.commit() | |
17 | # we use lower case username, but in db is Capitalized | |
18 | login_payload = { | |
19 | 'email': 'susan', | |
20 | 'password': 'pepito', | |
21 | } | |
22 | res = test_client.post('/login', data=login_payload) | |
23 | assert res.status_code == 200 | |
24 | assert 'authentication_token' in res.json['response']['user'] |
15 | 15 | @pytest.mark.usefixtures('logged_user') |
16 | 16 | class TestFileUpload(): |
17 | 17 | |
18 | def test_file_upload(self, test_client, session): | |
18 | def test_file_upload(self, test_client, session, csrf_token): | |
19 | 19 | ws = WorkspaceFactory.create(name="abc") |
20 | 20 | session.add(ws) |
21 | 21 | session.commit() |
26 | 26 | |
27 | 27 | with open(path,'r') as report: |
28 | 28 | file_contents = report.read() |
29 | session_response = test_client.get('/session') | |
30 | csrf_token = session_response.json.get('csrf_token') | |
31 | 29 | data = { |
32 | 30 | 'file' : (BytesIO(file_contents), 'nmap_report.xml'), |
33 | 31 | 'csrf_token' : csrf_token |
76 | 74 | assert res.status_code == 403 |
77 | 75 | |
78 | 76 | |
79 | def test_request_with_workspace_deactivate(self, test_client, session): | |
77 | def test_request_with_workspace_deactivate(self, test_client, session, csrf_token): | |
80 | 78 | ws = WorkspaceFactory.create(name="abc") |
81 | 79 | ws.active = False |
82 | 80 | session.add(ws) |
89 | 87 | with open(path,'r') as report: |
90 | 88 | file_contents = report.read() |
91 | 89 | |
92 | session_response = test_client.get('/session') | |
93 | csrf_token = session_response.json.get('csrf_token') | |
94 | 90 | data = { |
95 | 91 | 'file' : (BytesIO(file_contents), 'nmap_report.xml'), |
96 | 92 | 'csrf_token' : csrf_token |
1583 | 1583 | res = test_client.post(self.url(), data=data) |
1584 | 1584 | assert res.status_code == 201 |
1585 | 1585 | |
1586 | def test_add_attachment_to_vuln(self, test_client, session, host_with_hostnames): | |
1586 | def test_add_attachment_to_vuln(self, test_client, session, csrf_token, | |
1587 | host_with_hostnames): | |
1587 | 1588 | ws = WorkspaceFactory.create(name='abc') |
1588 | 1589 | session.add(ws) |
1589 | 1590 | vuln = VulnerabilityFactory.create(workspace=ws) |
1594 | 1595 | 'file' : (BytesIO(file_contents), 'borrar.txt') |
1595 | 1596 | } |
1596 | 1597 | headers = {'Content-type': 'multipart/form-data'} |
1598 | ||
1597 | 1599 | res = test_client.post( |
1598 | 1600 | '/v2/ws/abc/vulns/{0}/attachment/'.format(vuln.id), |
1599 | 1601 | data=data, headers=headers, use_json_data=False) |
1600 | assert res.status_code == 200 | |
1602 | assert res.status_code == 403 # Missing CSRF protection | |
1603 | ||
1604 | data = { | |
1605 | 'file' : (BytesIO(file_contents), 'borrar.txt'), | |
1606 | 'csrf_token': csrf_token | |
1607 | } | |
1608 | res = test_client.post( | |
1609 | '/v2/ws/abc/vulns/{0}/attachment/'.format(vuln.id), | |
1610 | data=data, headers=headers, use_json_data=False) | |
1611 | assert res.status_code == 200 # Now it should work | |
1612 | ||
1601 | 1613 | file_id = session.query(Vulnerability).filter_by(id=vuln.id).first().evidence[0].content['file_id'] |
1602 | 1614 | depot = DepotManager.get() |
1603 | 1615 | assert file_contents == depot.get(file_id).read() |
1616 | ||
1617 | def test_add_attachment_to_vuln_fails_readonly(self, test_client, session, host_with_hostnames): | |
1618 | ws = WorkspaceFactory.create(name='abc') | |
1619 | session.add(ws) | |
1620 | vuln = VulnerabilityFactory.create(workspace=ws) | |
1621 | session.add(vuln) | |
1622 | session.commit() | |
1623 | file_contents = 'my file contents' | |
1624 | data = { | |
1625 | 'file' : (BytesIO(file_contents), 'borrar.txt') | |
1626 | } | |
1627 | headers = {'Content-type': 'multipart/form-data'} | |
1628 | ||
1629 | ws.readonly = True | |
1630 | session.commit() | |
1631 | ||
1632 | res = test_client.post( | |
1633 | '/v2/ws/abc/vulns/{0}/attachment/'.format(vuln.id), | |
1634 | data=data, headers=headers, use_json_data=False) | |
1635 | assert res.status_code == 403 | |
1636 | query_test = session.query(Vulnerability).filter_by(id=vuln.id).first().evidence | |
1637 | assert query_test == [] | |
1604 | 1638 | |
1605 | 1639 | def test_delete_attachment_from_vuln(self, test_client, session, host_with_hostnames): |
1606 | 1640 | session.commit() # flush host_with_hostnames |
1633 | 1667 | query_test = session.query(Vulnerability).filter_by(id=vuln_id).first().evidence |
1634 | 1668 | assert query_test == [] |
1635 | 1669 | |
1670 | def test_delete_attachment_from_vuln_fails_readonly(self, test_client, session, host_with_hostnames): | |
1671 | session.commit() # flush host_with_hostnames | |
1672 | ws_name = host_with_hostnames.workspace.name | |
1673 | attachment = NamedTemporaryFile() | |
1674 | file_content = 'test file' | |
1675 | attachment.write(file_content) | |
1676 | attachment.seek(0) | |
1677 | vuln = _create_post_data_vulnerability( | |
1678 | name='Testing vuln', | |
1679 | vuln_type='Vulnerability', | |
1680 | parent_id=host_with_hostnames.id, | |
1681 | parent_type='Host', | |
1682 | refs=[], | |
1683 | policyviolations=[], | |
1684 | attachments=[attachment] | |
1685 | ) | |
1686 | res = test_client.post('/v2/ws/{workspace}/vulns/'\ | |
1687 | .format(workspace=ws_name), data=vuln) | |
1688 | assert res.status_code == 201 | |
1689 | ||
1690 | self.workspace.readonly = True | |
1691 | session.commit() | |
1692 | ||
1693 | filename = attachment.name.split('/')[-1] | |
1694 | vuln_id = res.json['_id'] | |
1695 | res = test_client.delete( | |
1696 | '/v2/ws/{workspace}/vulns/{vulnerability}/attachment/{file_name}/' | |
1697 | .format(workspace=ws_name, vulnerability=vuln_id, file_name=filename) | |
1698 | ) | |
1699 | assert res.status_code == 403 | |
1700 | ||
1701 | query_test = session.query(Vulnerability).filter_by(id=vuln_id).first().evidence | |
1702 | assert len(query_test) == 1 | |
1703 | assert query_test[0].filename == filename | |
1704 | ||
1636 | 1705 | @pytest.mark.skip("NplusOneError") |
1637 | 1706 | def test_vuln_filter(self, test_client, session, workspace): |
1638 | 1707 | new_host = HostFactory.create(workspace=workspace) |
1717 | 1786 | res = test_client.post(self.url(), data=raw_data) |
1718 | 1787 | assert res.json['messages']['_schema'][0] == 'Unknown parent type' |
1719 | 1788 | |
1720 | def test_add_empty_attachment(self, test_client, session, workspace): | |
1789 | def test_add_empty_attachment(self, test_client, session, workspace, csrf_token): | |
1721 | 1790 | vuln = VulnerabilityFactory.create(workspace=workspace) |
1722 | 1791 | session.add(vuln) |
1723 | 1792 | session.commit() |
1724 | 1793 | |
1725 | 1794 | res = test_client.post( |
1726 | 1795 | '/v2/ws/{ws_name}/vulns/{id}/attachment/' |
1727 | .format(ws_name=workspace.name,id=vuln.id)) | |
1796 | .format(ws_name=workspace.name,id=vuln.id), | |
1797 | data={'csrf_token': csrf_token}, | |
1798 | headers={'Content-Type': 'multipart/form-data'}, | |
1799 | use_json_data=False) | |
1728 | 1800 | assert res.status_code == 400 |
1729 | 1801 | |
1730 | 1802 | def test_get_attachment_with_invalid_workspace_and_vuln(self, test_client): |
78 | 78 | assert res.status_code == 200 |
79 | 79 | assert len(res.json['data']) == OBJECT_COUNT |
80 | 80 | |
81 | def test_can_list_readonly(self, test_client, session): | |
82 | self.workspace.readonly = True | |
83 | session.commit() | |
84 | res = test_client.get(self.url()) | |
85 | assert res.status_code == 200 | |
81 | 86 | |
82 | 87 | class RetrieveTestsMixin: |
83 | 88 | |
112 | 117 | object_id = res.json['id'] |
113 | 118 | obj = self.model.query.get(object_id) |
114 | 119 | assert obj.workspace == self.workspace |
120 | ||
121 | def test_create_fails_readonly(self, test_client): | |
122 | self.workspace.readonly = True | |
123 | db.session.commit() | |
124 | data = self.factory.build_dict(workspace=self.workspace) | |
125 | res = test_client.post(self.url(), | |
126 | data=data) | |
127 | db.session.commit() | |
128 | assert res.status_code == 403 | |
129 | assert self.model.query.count() == OBJECT_COUNT | |
130 | ||
115 | 131 | |
116 | 132 | def test_create_inactive_fails(self, test_client): |
117 | 133 | self.workspace.deactivate() |
163 | 179 | assert res.json[updated_field] == getattr(self.first_object, |
164 | 180 | updated_field) |
165 | 181 | |
182 | def test_update_an_object_readonly_fails(self, test_client): | |
183 | self.workspace.readonly = True | |
184 | db.session.commit() | |
185 | for unique_field in self.unique_fields: | |
186 | data = self.factory.build_dict() | |
187 | old_field = getattr(self.objects[0], unique_field) | |
188 | old_id = getattr(self.objects[0], 'id') | |
189 | res = test_client.put(self.url(self.first_object), data=data) | |
190 | db.session.commit() | |
191 | assert res.status_code == 403 | |
192 | assert self.model.query.count() == OBJECT_COUNT | |
193 | assert old_field == getattr(self.model.query.filter(self.model.id == old_id).one(), unique_field) | |
194 | ||
166 | 195 | def test_update_inactive_fails(self, test_client): |
167 | 196 | self.workspace.deactivate() |
168 | 197 | db.session.commit() |
207 | 236 | assert was_deleted(self.first_object) |
208 | 237 | assert self.model.query.count() == OBJECT_COUNT - 1 |
209 | 238 | |
239 | def test_delete_readonly_fails(self, test_client, session): | |
240 | self.workspace.readonly = True | |
241 | session.commit() | |
242 | res = test_client.delete(self.url(self.first_object)) | |
243 | assert res.status_code == 403 # No content | |
244 | assert not was_deleted(self.first_object) | |
245 | assert self.model.query.count() == OBJECT_COUNT | |
246 | ||
210 | 247 | def test_delete_inactive_fails(self, test_client): |
211 | 248 | self.workspace.deactivate() |
212 | 249 | db.session.commit() |
3 | 3 | import subprocess |
4 | 4 | from datetime import datetime |
5 | 5 | from server.utils import daemonize |
6 | import server.config | |
7 | ||
8 | try: | |
9 | import ConfigParser | |
10 | except ImportError: | |
11 | import configparser as ConfigParser | |
6 | 12 | |
7 | 13 | |
8 | 14 | def test_start_and_kill_faraday_server(): |
21 | 27 | if server_port > 6500: |
22 | 28 | raise Exception('No free ports could be found') |
23 | 29 | |
30 | if 'POSTGRES_DB' in os.environ: | |
31 | # I'm on gitlab ci runner | |
32 | # I will overwrite server.ini | |
33 | connection_string = 'postgresql+psycopg2://{username}:{password}@postgres/{database}'.format( | |
34 | username=os.environ['POSTGRES_USER'], | |
35 | password=os.environ['POSTGRES_PASSWORD'], | |
36 | database=os.environ['POSTGRES_DB'], | |
37 | ) | |
38 | faraday_config = ConfigParser.SafeConfigParser() | |
39 | config_path = os.path.expanduser('~/.faraday/config/server.ini') | |
40 | faraday_config.read(config_path) | |
41 | try: | |
42 | faraday_config.add_section('database') | |
43 | except ConfigParser.DuplicateSectionError: | |
44 | pass | |
45 | faraday_config.set('database', 'connection_string', connection_string) | |
46 | with open(config_path, 'w') as faraday_config_file: | |
47 | faraday_config.write(faraday_config_file) | |
48 | manage_script = os.path.join(current_path, '..', 'manage.py') | |
49 | command = ['/usr/bin/env', 'python2.7', manage_script, 'create-tables'] | |
50 | subproc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd= os.path.join(current_path, '..')) | |
51 | subproc.wait() | |
52 | std, err = subproc.communicate() | |
53 | assert subproc.returncode == 0, ('Create tables failed!', std, err) | |
54 | ||
24 | 55 | server_script = os.path.join(current_path, '..', 'faraday-server.py') |
25 | command = ['/usr/bin/env', 'python2.7', server_script, '--port', '{0}'.format(server_port)] | |
56 | command = ['/usr/bin/env', 'python2.7', server_script, '--port', '{0}'.format(server_port), '--debug'] | |
26 | 57 | subproc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
27 | 58 | start = datetime.now() |
28 | 59 | while subproc.returncode is None: |
29 | 60 | now = datetime.now() |
30 | 61 | delta = now - start |
31 | if delta.seconds > 40: | |
62 | if delta.seconds > 140: | |
32 | 63 | raise UserWarning('Faraday server test timeout!') |
33 | if delta.seconds > 4: | |
64 | if delta.seconds > 30: | |
34 | 65 | subproc.send_signal(signal.SIGTERM) |
35 | 66 | subproc.wait() |
36 | 67 | subproc.poll() |
38 | 69 | subproc.poll() |
39 | 70 | time.sleep(0.1) |
40 | 71 | out, err = subproc.communicate() |
41 | assert subproc.returncode == 0, err | |
72 | if subproc.returncode != 0: | |
73 | log_path = os.path.expanduser('~/.faraday/logs/faraday-server.log') | |
74 | with open(log_path, 'r') as log_file: | |
75 | print(log_file.read()) | |
76 | assert subproc.returncode == 0, (out, err, command, server_script) |