diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 6f15b22..131c639 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -1,7 +1,7 @@ name: Linting on: [push, pull_request] jobs: - lint: + lint: # Run per push for internal contributers. This isn't possible for forked pull requests, # so we'll need to run on PR events for external contributers. # String comparison below is case insensitive. diff --git a/.github/workflows/review-release.yml b/.github/workflows/review-release.yml new file mode 100644 index 0000000..6f3bf31 --- /dev/null +++ b/.github/workflows/review-release.yml @@ -0,0 +1,22 @@ +name: Review Release +concurrency: + group: app-release + cancel-in-progress: true +permissions: + contents: read + id-token: write + statuses: write +on: + workflow_dispatch: + inputs: + task_token: + description: 'StepFunction task token' + required: true + +jobs: + review: + uses: 'phantomcyber/dev-cicd-tools/.github/workflows/review-release.yml@main' + with: + task_token: ${{ inputs.task_token }} + secrets: + resume_release_role_arn: ${{ secrets.RESUME_RELEASE_ROLE_ARN }} diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 23d31c5..712cc1b 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -1,5 +1,5 @@ name: Semgrep -on: +on: pull_request_target: branches: - next @@ -21,8 +21,8 @@ jobs: echo "REPOSITORY=${{ github.event.pull_request.head.repo.full_name }}" >> $GITHUB_ENV echo "REF=${{ github.event.pull_request.head.ref }}" >> $GITHUB_ENV - uses: 'phantomcyber/dev-cicd-tools/github-actions/semgrep@main' - with: + with: SEMGREP_DEPLOYMENT_ID: ${{ secrets.SEMGREP_DEPLOYMENT_ID }} SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} - REPOSITORY: ${{ github.repository }} + REPOSITORY: ${{ github.repository }} REF: ${{ github.ref }} diff --git a/.github/workflows/start-release.yml b/.github/workflows/start-release.yml index d5fb354..7bbce79 100644 --- a/.github/workflows/start-release.yml +++ b/.github/workflows/start-release.yml @@ -1,9 +1,13 @@ name: Start Release -on: workflow_dispatch +on: + workflow_dispatch: + push: + tags: + - '*-beta*' jobs: start-release: runs-on: ubuntu-latest steps: - uses: 'phantomcyber/dev-cicd-tools/github-actions/start-release@main' with: - GITHUB_TOKEN: ${{ secrets.SOAR_APPS_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.SOAR_APPS_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bb6f9ad..13ffd89 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,11 +1,11 @@ repos: - repo: https://github.com/phantomcyber/dev-cicd-tools - rev: v1.4 + rev: v1.13 hooks: - id: org-hook - id: package-app-dependencies - repo: https://github.com/Yelp/detect-secrets - rev: v1.1.0 + rev: v1.3.0 hooks: - id: detect-secrets args: ['--no-verify', '--exclude-files', '^cybereason.json$'] diff --git a/LICENSE b/LICENSE index f003b93..fea88a6 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2021 Splunk Inc. + Copyright (c) Cybereason, 2018-2021 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/NOTICE b/NOTICE index acb6261..54846cb 100644 --- a/NOTICE +++ b/NOTICE @@ -1,20 +1,15 @@ Splunk SOAR Cybereason -Copyright 2021 Splunk Inc. +Copyright (c) Cybereason, 2018-2021 Third-party Software Attributions: Library: beautifulsoup4 Version: 4.9.1 License: MIT -Copyright 2004-2019 Leonard Richardson Copyright 2004-2017 Leonard Richardson +Copyright 2004-2019 Leonard Richardson Copyright 2018 Isaac Muse -Library: pudb -Version: 2019.2 -License: MIT -Copyright 2009 Andreas Kloeckner and contributors - Library: requests Version: 2.25.0 License: Apache 2.0 diff --git a/README.md b/README.md new file mode 100644 index 0000000..001d114 --- /dev/null +++ b/README.md @@ -0,0 +1,623 @@ +[comment]: # "Auto-generated SOAR connector documentation" +# Cybereason + +Publisher: Cybereason +Connector Version: 2\.2\.0 +Product Vendor: Cybereason +Product Name: Cybereason +Product Version Supported (regex): "\.\*" +Minimum Product Version: 5\.3\.0 + +This app integrates with the Cybereason platform to perform investigative, contain, and corrective actions on Malop and Malware events + +[comment]: # " File: README.md" +[comment]: # "" +[comment]: # " Licensed under the Apache License, Version 2.0 (the 'License');" +[comment]: # " you may not use this file except in compliance with the License." +[comment]: # " You may obtain a copy of the License at" +[comment]: # "" +[comment]: # " http://www.apache.org/licenses/LICENSE-2.0" +[comment]: # "" +[comment]: # " Unless required by applicable law or agreed to in writing, software distributed under" +[comment]: # " the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND," +[comment]: # " either express or implied. See the License for the specific language governing permissions" +[comment]: # " and limitations under the License." +[comment]: # "" +## Overview + +The Cybereason platform finds a single component of an attack and connects it to other pieces of +information to reveal an entire campaign and shut it down. There are two types of alerts that +Cybereason will create: + +- Malops: This stands for a Malicious Operation, and will describe machines, users, processes, and + connections used in the attack. +- Malware: These alerts are generated when a user tries to run a piece of malware. + +## Playbook Backward Compatibility + +- The below-mentioned actions have been added. Hence, it is requested to the end-user to please + update their existing playbooks by inserting \| modifying \| deleting the corresponding action + blocks for this action on the earlier versions of the app. + - isolate specific machine + - unisolate specific machine + - upgrade sensor + - restart sensor + - query machine ip + + +### Configuration Variables +The below configuration variables are required for this Connector to operate. These variables are specified when configuring a Cybereason asset in SOAR. + +VARIABLE | REQUIRED | TYPE | DESCRIPTION +-------- | -------- | ---- | ----------- +**base\_url** | required | string | The URL of the Cybereason server to connect to\. This should be of the form 'https\://\:' +**verify\_server\_cert** | optional | boolean | If checked, will verify the SSL certificate of the Cybereason server +**username** | required | string | A valid username for connecting to the Cybereason server +**password** | required | password | A valid password for connecting to the Cybereason server +**malop\_historical\_days** | required | numeric | The number of days for which we want to get Malops \(This parameter will be used for the first\-time poll only, and will be ignored in subsequent polls\) +**malware\_historical\_days** | required | numeric | The number of days for which we want to get Malware \(This parameter will be used for the first\-time poll only, and will be ignored in subsequent polls\) +**override\_malop\_severity\_map** | optional | string | A JSON string that the user can add to override the default severity mapping for different malop types +**malware\_severity** | optional | string | The severity to apply for all malware events + +### Supported Actions +[test connectivity](#action-test-connectivity) - Validate the asset configuration for connectivity using supplied configuration +[on poll](#action-on-poll) - Callback action for the on\_poll ingest functionality +[delete registry key](#action-delete-registry-key) - Deletes the specified registry key for a given malop ID and machine name +[get sensor status](#action-get-sensor-status) - Get the connectivity status for all machine sensors in a Malop +[add malop comment](#action-add-malop-comment) - Add a comment to the provided Malop ID +[update malop status](#action-update-malop-status) - Update status for the provided Malop ID such as Under Investigation, To review, etc +[isolate machine](#action-isolate-machine) - Blocks all communication to and from the machine\. Communication with the Cybereason platform is not affected +[unisolate machine](#action-unisolate-machine) - Unblocks all communication to and from the machine +[isolate specific machine](#action-isolate-specific-machine) - Blocks all communication to and from the machine identified by the given Name or IP\. Communication with the Cybereason platform is not affected +[unisolate specific machine](#action-unisolate-specific-machine) - Unblocks all communication to and from the machine identified by the given Name or IP\. Communication with the Cybereason platform is not affected +[kill process](#action-kill-process) - Kills the active process on the machine +[get remediation status](#action-get-remediation-status) - Gets the remediation status for a previously executed remediation action like Kill Process +[set reputation](#action-set-reputation) - Blacklists / Whitelists / Removes a file hash reputation so that future malop detections can quickly identify the hash +[query processes](#action-query-processes) - Queries a given malop to retrieve all processes +[query machine](#action-query-machine) - Queries a given machine name to retrieve all that machine's information +[query machine ip](#action-query-machine-ip) - Queries a given machine IP to retrieve all that machine's information +[query users](#action-query-users) - Queries a given user to retrieve all user\-related details +[query files](#action-query-files) - Queries a given filename to retrieve all file details +[query domain](#action-query-domain) - Queries a given domain name to retrieve all details of that domain +[query connections](#action-query-connections) - Queries a given name to retrieve all details of that connection +[upgrade sensor](#action-upgrade-sensor) - Upgrade a sensor +[restart sensor](#action-restart-sensor) - Restart a sensor + +## action: 'test connectivity' +Validate the asset configuration for connectivity using supplied configuration + +Type: **test** +Read only: **True** + +Test the connectivity with the Cybereason server configured by the user\. + +#### Action Parameters +No parameters are required for this action + +#### Action Output +No Output + +## action: 'on poll' +Callback action for the on\_poll ingest functionality + +Type: **ingest** +Read only: **True** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**container\_count** | optional | Maximum number of containers to ingest | numeric | +**container\_id** | optional | Parameter ignored in this app | string | +**start\_time** | optional | Parameter ignored in this app | numeric | +**end\_time** | optional | Parameter ignored in this app | numeric | +**artifact\_count** | optional | Parameter ignored in this app | numeric | + +#### Action Output +No Output + +## action: 'delete registry key' +Deletes the specified registry key for a given malop ID and machine name + +Type: **correct** +Read only: **False** + +A malop can contain processes that write to Windows registry keys\. This action will fire a remediation action on the Cybereason console that will delete the registry key using the Cybereason sensor installed on the machine\. + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**malop\_id** | required | The ID of the malop for deleting the registry key | string | `cybereason malop id` +**machine\_name** | required | The name of the machine on which we want to delete the registry key | string | `cybereason machine name` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.parameter\.malop\_id | string | `cybereason malop id` +action\_result\.parameter\.machine\_name | string | `cybereason machine name` +action\_result\.data\.\*\.remediation\_id | string | `cybereason remediation id` +action\_result\.data\.\*\.initiating\_user | string | `cybereason user` +action\_result\.status | string | +action\_result\.message | string | +action\_result\.summary | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'get sensor status' +Get the connectivity status for all machine sensors in a Malop + +Type: **investigate** +Read only: **True** + +Each machine covered by Cybereason will have a sensor installed on it\. This action will extract the sensor status for all machines in a Malop\. + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**malop\_id** | required | The malop ID for which we will get sensor status information | string | `cybereason malop id` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.parameter\.malop\_id | string | `cybereason malop id` +action\_result\.data\.\*\.machine\_name | string | `cybereason machine name` +action\_result\.data\.\*\.machine\_id | string | `cybereason machine id` +action\_result\.data\.\*\.status | string | `cybereason sensor status` +action\_result\.status | string | +action\_result\.message | string | +action\_result\.summary | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'add malop comment' +Add a comment to the provided Malop ID + +Type: **generic** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**malop\_id** | required | The malop ID for which we want to add a comment | string | `cybereason malop id` +**comment** | optional | The comment that we want to add to the malop | string | + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.parameter\.malop\_id | string | `cybereason malop id` +action\_result\.parameter\.comment | string | +action\_result\.data | string | +action\_result\.status | string | +action\_result\.message | string | +action\_result\.summary | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'update malop status' +Update status for the provided Malop ID such as Under Investigation, To review, etc + +Type: **generic** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**malop\_id** | required | The malop ID for which we want to update the status | string | `cybereason malop id` +**status** | required | The status that will be assigned to the malop | string | `cybereason malop status` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.parameter\.malop\_id | string | `cybereason malop id` +action\_result\.parameter\.status | string | `cybereason malop status` +action\_result\.data | string | +action\_result\.status | string | +action\_result\.message | string | +action\_result\.summary | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'isolate machine' +Blocks all communication to and from the machine\. Communication with the Cybereason platform is not affected + +Type: **contain** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**malop\_id** | required | The malop ID for which we want to isolate the machine | string | `cybereason malop id` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.parameter\.malop\_id | string | `cybereason malop id` +action\_result\.data | string | +action\_result\.status | string | +action\_result\.message | string | +action\_result\.summary | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'unisolate machine' +Unblocks all communication to and from the machine + +Type: **correct** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**malop\_id** | required | The malop ID for which we want to unisolate the machine | string | `cybereason malop id` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.parameter\.malop\_id | string | `cybereason malop id` +action\_result\.data | string | +action\_result\.status | string | +action\_result\.message | string | +action\_result\.summary | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'isolate specific machine' +Blocks all communication to and from the machine identified by the given Name or IP\. Communication with the Cybereason platform is not affected + +Type: **contain** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**machine\_name\_or\_ip** | required | Name or IP of the machine that needs to be isolated | string | `cybereason machine name or ip` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.parameter\.machine\_name\_or\_ip | string | `cybereason machine name or ip` +action\_result\.data | string | +action\_result\.status | string | +action\_result\.message | string | +action\_result\.summary | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'unisolate specific machine' +Unblocks all communication to and from the machine identified by the given Name or IP\. Communication with the Cybereason platform is not affected + +Type: **correct** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**machine\_name\_or\_ip** | required | Name or IP of the machine that needs to be unisolated | string | `cybereason machine name or ip` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.parameter\.machine\_name\_or\_ip | string | `cybereason machine name or ip` +action\_result\.data | string | +action\_result\.status | string | +action\_result\.message | string | +action\_result\.summary | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'kill process' +Kills the active process on the machine + +Type: **contain** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**malop\_id** | required | The malop ID for which we want to kill the active process | string | `cybereason malop id` +**remediation\_user** | required | The user\-id who is killing the process | string | `cybereason user` +**machine\_id** | required | Machine ID associated with that malop ID | string | `cybereason machine id` +**process\_id** | required | Cybereason Process ID of the process to kill | string | `cybereason process id` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.parameter\.malop\_id | string | `cybereason malop id` +action\_result\.parameter\.remediation\_user | string | `cybereason user` +action\_result\.parameter\.machine\_id | string | `cybereason machine id` +action\_result\.parameter\.process\_id | string | `cybereason process id` +action\_result\.data\.\*\.remediation\_id | string | `cybereason remediation id` +action\_result\.data\.\*\.remediation\_status | string | `cybereason remediation status` +action\_result\.status | string | +action\_result\.message | string | +action\_result\.summary | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'get remediation status' +Gets the remediation status for a previously executed remediation action like Kill Process + +Type: **investigate** +Read only: **True** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**malop\_id** | required | The malop ID for which we want to get the remediation status | string | `cybereason malop id` +**remediation\_user** | required | The user ID that has requested the remediation action | string | `cybereason user` +**remediation\_id** | required | An ID that specifies a previously executed remediation action like Kill Process | string | `cybereason remediation id` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.parameter\.malop\_id | string | `cybereason malop id` +action\_result\.parameter\.remediation\_user | string | `cybereason user` +action\_result\.parameter\.remediation\_id | string | `cybereason remediation id` +action\_result\.data\.\*\.remediation\_status | string | `cybereason remediation status` +action\_result\.data\.\*\.remediation\_message | string | +action\_result\.status | string | +action\_result\.message | string | +action\_result\.summary | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'set reputation' +Blacklists / Whitelists / Removes a file hash reputation so that future malop detections can quickly identify the hash + +Type: **generic** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**reputation\_item\_hash** | required | The item \(hash, IP\) for which we want to set the reputation | string | `hash` +**custom\_reputation** | required | The custom reputation that we want to set for the reputation item | string | + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.parameter\.reputation\_item\_hash | string | `hash` +action\_result\.parameter\.custom\_reputation | string | +action\_result\.data | string | +action\_result\.status | string | +action\_result\.message | string | +action\_result\.summary | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'query processes' +Queries a given malop to retrieve all processes + +Type: **investigate** +Read only: **True** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**malop\_id** | required | The ID of the malop for which we want to get process details | string | `cybereason malop id` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.parameter\.malop\_id | string | `cybereason malop id` +action\_result\.data\.\*\.process\_id | string | `cybereason process id` +action\_result\.data\.\*\.process\_name | string | `cybereason process name` +action\_result\.data\.\*\.owner\_machine\_id | string | `cybereason machine id` +action\_result\.data\.\*\.owner\_machine\_name | string | `cybereason machine name` +action\_result\.status | string | +action\_result\.message | string | +action\_result\.summary | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'query machine' +Queries a given machine name to retrieve all that machine's information + +Type: **investigate** +Read only: **True** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**name** | required | The name of the machine | string | + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.parameter\.name | string | +action\_result\.data\.\*\.machine\_id | string | `cybereason machine id` +action\_result\.data\.\*\.machine\_name | string | +action\_result\.data\.\*\.os\_version | string | +action\_result\.data\.\*\.platform\_architecture | string | +action\_result\.data\.\*\.is\_connected\_to\_cybereason | string | +action\_result\.status | string | +action\_result\.message | string | +action\_result\.summary | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'query machine ip' +Queries a given machine IP to retrieve all that machine's information + +Type: **investigate** +Read only: **True** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**machine\_ip** | required | The IP of a machine | string | `ip` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.parameter\.machine\_ip | string | `ip` +action\_result\.data\.\*\.machine\_id | string | `cybereason machine id` +action\_result\.data\.\*\.machine\_name | string | +action\_result\.data\.\*\.os\_version | string | +action\_result\.data\.\*\.platform\_architecture | string | +action\_result\.data\.\*\.is\_connected\_to\_cybereason | string | +action\_result\.status | string | +action\_result\.message | string | +action\_result\.summary | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'query users' +Queries a given user to retrieve all user\-related details + +Type: **investigate** +Read only: **True** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**user** | required | The name of the user | string | `cybereason user` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.parameter\.user | string | `cybereason user` +action\_result\.data\.\*\.element\_name | string | +action\_result\.data\.\*\.domain | string | `domain` +action\_result\.data\.\*\.last\_machine\_logged\_into | string | +action\_result\.data\.\*\.organization | string | +action\_result\.data\.\*\.local\_system | string | +action\_result\.status | string | +action\_result\.message | string | +action\_result\.summary | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'query files' +Queries a given filename to retrieve all file details + +Type: **investigate** +Read only: **True** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**file\_name** | required | The name of the file | string | `file name` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.parameter\.file\_name | string | `file name` +action\_result\.data\.\*\.element\_name | string | +action\_result\.data\.\*\.suspicion\_count | string | +action\_result\.data\.\*\.signed | string | +action\_result\.data\.\*\.SHA1\_signature | string | +action\_result\.data\.\*\.size | string | +action\_result\.data\.\*\.path | string | +action\_result\.data\.\*\.product\_name | string | +action\_result\.data\.\*\.company\_name | string | +action\_result\.status | string | +action\_result\.message | string | +action\_result\.summary | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'query domain' +Queries a given domain name to retrieve all details of that domain + +Type: **investigate** +Read only: **True** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**domain\_name** | required | The name of the domain | string | `domain` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.parameter\.domain\_name | string | `domain` +action\_result\.data\.\*\.element\_name | string | +action\_result\.data\.\*\.malicious\_classification\_type | string | +action\_result\.data\.\*\.is\_internal\_domain | string | +action\_result\.data\.\*\.was\_ever\_resolved | string | +action\_result\.data\.\*\.was\_ever\_resolved\_as\_second\_level\_domain | string | +action\_result\.status | string | +action\_result\.message | string | +action\_result\.summary | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'query connections' +Queries a given name to retrieve all details of that connection + +Type: **investigate** +Read only: **True** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**connection\_name** | required | The name of the connection | string | + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.parameter\.connection\_name | string | +action\_result\.data\.\*\.element\_name | string | +action\_result\.data\.\*\.direction | string | +action\_result\.data\.\*\.server\_address | string | +action\_result\.data\.\*\.server\_port | string | +action\_result\.data\.\*\.port\_type | string | `port` +action\_result\.data\.\*\.received\_bytes | string | +action\_result\.data\.\*\.transmitted\_bytes | string | +action\_result\.data\.\*\.remote\_address | string | +action\_result\.data\.\*\.owner\_machine | string | +action\_result\.data\.\*\.owner\_process | string | +action\_result\.data\.\*\.dns\_query | string | +action\_result\.status | string | +action\_result\.message | string | +action\_result\.summary | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'upgrade sensor' +Upgrade a sensor + +Type: **generic** +Read only: **False** + +Upgrade a sensor using the Cybereason sensor pylum ID provided as an input\. + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**pylumid** | required | The Cybereason sensor pylum ID targeted for upgrade \(comma\-separated IDs allowed\) | string | `cybereason sensor pylum id` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.parameter\.pylumid | string | `cybereason sensor pylum id` +action\_result\.data | string | +action\_result\.status | string | +action\_result\.message | string | +action\_result\.summary | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | + +## action: 'restart sensor' +Restart a sensor + +Type: **generic** +Read only: **False** + +Restart a sensor using the Cybereason sensor pylum ID provided as an input\. + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**pylumid** | required | The Cybereason sensor pylum ID targeted for restart \(comma\-separated IDs allowed\) | string | `cybereason sensor pylum id` + +#### Action Output +DATA PATH | TYPE | CONTAINS +--------- | ---- | -------- +action\_result\.parameter\.pylumid | string | `cybereason sensor pylum id` +action\_result\.data | string | +action\_result\.status | string | +action\_result\.message | string | +action\_result\.summary | string | +summary\.total\_objects | numeric | +summary\.total\_objects\_successful | numeric | \ No newline at end of file diff --git a/cybereason.json b/cybereason.json index 09d1eee..24b8509 100644 --- a/cybereason.json +++ b/cybereason.json @@ -11,12 +11,13 @@ "product_version_regex": ".*", "publisher": "Cybereason", "license": "Copyright (c) Cybereason, 2018-2021", - "app_version": "2.1.3", - "utctime_updated": "2021-11-29T07:29:57.000000Z", + "app_version": "2.2.0", + "utctime_updated": "2022-01-07T20:19:08.000000Z", "package_name": "phantom_cybereason", "main_module": "cybereason_connector.py", - "min_phantom_version": "5.0.0", + "min_phantom_version": "5.3.0", "app_wizard_version": "1.0.0", + "fips_compliant": false, "configuration": { "base_url": { "description": "The URL of the Cybereason server to connect to. This should be of the form 'https://:'", @@ -1817,7 +1818,7 @@ { "data_path": "action_result.data.*.dns_query", "data_type": "string", - "column_name": "DNS Query", + "column_name": "Dns Query", "column_order": 10 }, { @@ -2017,32 +2018,32 @@ "wheel": [ { "module": "beautifulsoup4", - "input_file": "wheels/beautifulsoup4-4.9.1-py3-none-any.whl" + "input_file": "wheels/py3/beautifulsoup4-4.9.1-py3-none-any.whl" }, { "module": "certifi", - "input_file": "wheels/certifi-2021.10.8-py2.py3-none-any.whl" + "input_file": "wheels/py3/certifi-2022.6.15-py3-none-any.whl" }, { "module": "chardet", - "input_file": "wheels/chardet-3.0.4-py2.py3-none-any.whl" + "input_file": "wheels/shared/chardet-3.0.4-py2.py3-none-any.whl" }, { "module": "idna", - "input_file": "wheels/idna-2.10-py2.py3-none-any.whl" + "input_file": "wheels/shared/idna-2.10-py2.py3-none-any.whl" }, { "module": "requests", - "input_file": "wheels/requests-2.25.0-py2.py3-none-any.whl" + "input_file": "wheels/shared/requests-2.25.0-py2.py3-none-any.whl" }, { "module": "soupsieve", - "input_file": "wheels/soupsieve-2.3-py3-none-any.whl" + "input_file": "wheels/py3/soupsieve-2.3.2.post1-py3-none-any.whl" }, { "module": "urllib3", - "input_file": "wheels/urllib3-1.26.7-py2.py3-none-any.whl" + "input_file": "wheels/shared/urllib3-1.26.11-py2.py3-none-any.whl" } ] } -} \ No newline at end of file +} diff --git a/cybereason_connector.py b/cybereason_connector.py index 9ffdbed..1097136 100644 --- a/cybereason_connector.py +++ b/cybereason_connector.py @@ -129,6 +129,7 @@ def _validate_integer(self, action_result, parameter, key): return phantom.APP_SUCCESS, parameter def _handle_test_connectivity(self, param): + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(dict(param))) @@ -143,6 +144,7 @@ def _handle_test_connectivity(self, param): 'Successfully connected to the Cybereason console and verified session cookie' ) else: + self.debug_print('Failure to verify session cookie.') return action_result.set_status( phantom.APP_ERROR, 'Connectivity failed. Unable to get session cookie from Cybereason console' @@ -235,6 +237,7 @@ def _handle_delete_registry_key(self, param): }) except Exception as e: err = self._get_error_message_from_exception(e) + self.debug_print("Error occurred: {}".format(err)) return action_result.set_status(phantom.APP_ERROR, "Error occurred. {}".format(err)) return action_result.set_status(phantom.APP_SUCCESS) @@ -350,8 +353,8 @@ def _handle_update_malop_status(self, param): malop_id = self._get_string_param(param['malop_id']) - phantom_status = param['status'] - cybereason_status = PHANTOM_TO_CYBEREASON_STATUS.get(phantom_status) + soar_status = param['status'] + cybereason_status = SOAR_TO_CYBEREASON_STATUS.get(soar_status) if not cybereason_status: self.save_progress("Invalid status selected") return action_result.set_status( @@ -945,6 +948,54 @@ def _get_machine_name_by_machine_ip(self, machine_ip, action_result): return RetVal(action_result.set_status(phantom.APP_SUCCESS), machine_names) + def on_poll(self, param): + self.save_progress("Entered the on_poll function") + self.save_progress("processing") + poller = CybereasonPoller() + return poller.do_poll(self, param) + + def _handle_query_processes(self, param): + self.save_progress("Entered the _handle_query_processes function") + self.save_progress("processing") + query_action = CybereasonQueryActions() + return query_action._handle_query_processes(self, param) + + def _handle_query_machine(self, param): + self.save_progress("Entered the _handle_query_machine function") + self.save_progress("processing") + query_action = CybereasonQueryActions() + return query_action._handle_query_machine(self, param) + + def _handle_query_machine_ip(self, param): + self.save_progress("Entered the _handle_query_machine_ip function") + self.save_progress("processing") + query_action = CybereasonQueryActions() + return query_action._handle_query_machine_ip(self, param) + + def _handle_query_users(self, param): + self.save_progress("Entered the _handle_query_users function") + self.save_progress("processing") + query_action = CybereasonQueryActions() + return query_action._handle_query_users(self, param) + + def _handle_query_files(self, param): + self.save_progress("Entered the _handle_query_files function") + self.save_progress("processing") + query_action = CybereasonQueryActions() + return query_action._handle_query_files(self, param) + + def _handle_query_domain(self, param): + self.save_progress("Entered the _handle_query_domain function") + self.save_progress("processing") + query_action = CybereasonQueryActions() + return query_action._handle_query_domain(self, param) + + def _handle_query_connections(self, param): + self.save_progress("Entered the _handle_query_connections function") + self.save_progress("processing") + query_action = CybereasonQueryActions() + return query_action._handle_query_connections(self, param) + def handle_action(self, param): ret_val = phantom.APP_SUCCESS @@ -963,8 +1014,7 @@ def handle_action(self, param): ret_val = self._handle_get_sensor_status(param) elif action_id == 'on_poll': - poller = CybereasonPoller() - ret_val = poller.do_poll(self, param) + ret_val = self.on_poll(param) elif action_id == 'add_malop_comment': ret_val = self._handle_add_malop_comment(param) @@ -1000,32 +1050,25 @@ def handle_action(self, param): ret_val = self._handle_restart_sensor(param) elif action_id == 'query_processes': - query_action = CybereasonQueryActions() - ret_val = query_action._handle_query_processes(self, param) + ret_val = self._handle_query_processes(param) elif action_id == 'query_machine': - query_action = CybereasonQueryActions() - ret_val = query_action._handle_query_machine(self, param) + ret_val = self._handle_query_machine(param) elif action_id == 'query_machine_ip': - query_action = CybereasonQueryActions() - ret_val = query_action._handle_query_machine_ip(self, param) + ret_val = self._handle_query_machine_ip(param) elif action_id == 'query_users': - query_action = CybereasonQueryActions() - ret_val = query_action._handle_query_users(self, param) + ret_val = self._handle_query_users(param) elif action_id == 'query_files': - query_action = CybereasonQueryActions() - ret_val = query_action._handle_query_files(self, param) + ret_val = self._handle_query_files(param) elif action_id == 'query_domain': - query_action = CybereasonQueryActions() - ret_val = query_action._handle_query_domain(self, param) + ret_val = self._handle_query_domain(param) elif action_id == 'query_connections': - query_action = CybereasonQueryActions() - ret_val = query_action._handle_query_connections(self, param) + ret_val = self._handle_query_connections(param) return ret_val @@ -1055,6 +1098,7 @@ def finalize(self): def main(): import argparse + import sys import pudb @@ -1083,7 +1127,7 @@ def main(): login_url = CybereasonConnector._get_phantom_base_url() + '/login' print("Accessing the Login page") - r = requests.get(login_url, verify=False) + r = requests.get(login_url, timeout=DEFAULT_REQUEST_TIMEOUT) csrftoken = r.cookies['csrftoken'] data = dict() @@ -1096,11 +1140,11 @@ def main(): headers['Referer'] = login_url print("Logging into Platform to get the session id") - r2 = requests.post(login_url, verify=False, data=data, headers=headers) + r2 = requests.post(login_url, data=data, headers=headers, timeout=DEFAULT_REQUEST_TIMEOUT) session_id = r2.cookies['sessionid'] except Exception as e: print("Unable to get session id from the platform. Error: " + str(e)) - exit(1) + sys.exit(1) with open(args.input_test_json) as f: in_json = f.read() @@ -1117,7 +1161,7 @@ def main(): ret_val = connector._handle_action(json.dumps(in_json), None) print(json.dumps(json.loads(ret_val), indent=4)) - exit(0) + sys.exit(0) if __name__ == '__main__': diff --git a/cybereason_consts.py b/cybereason_consts.py index 1d9b1fa..fcb827c 100644 --- a/cybereason_consts.py +++ b/cybereason_consts.py @@ -11,7 +11,7 @@ # either express or implied. See the License for the specific language governing permissions # and limitations under the License. -PHANTOM_TO_CYBEREASON_STATUS = { +SOAR_TO_CYBEREASON_STATUS = { 'Unread': "UNREAD", 'To Review': "TODO", 'Not Relevant': "FP", @@ -31,3 +31,5 @@ MALOP_HISTORICAL_DAYS_KEY = "malop_historical_days asset configuration parameter" MALWARE_HISTORICAL_DAYS_KEY = "malware_historical_days asset configuration parameter" + +DEFAULT_REQUEST_TIMEOUT = 60 # in seconds diff --git a/cybereason_poller.py b/cybereason_poller.py old mode 100644 new mode 100755 index 77752ce..aee243a --- a/cybereason_poller.py +++ b/cybereason_poller.py @@ -368,14 +368,15 @@ def _ingest_malop(self, connector, config, malop_id, malop_data): return phantom.APP_SUCCESS if success else phantom.APP_ERROR - def _does_container_exist_for_malop_malware(self, connector, malop_id): + def _does_container_exist_for_malop_malware(self, connector, source_data_identifier): url = '{0}rest/container?_filter_source_data_identifier="{1}"&_filter_asset={2}'.format( connector.get_phantom_base_url(), - malop_id, connector.get_asset_id() + source_data_identifier, connector.get_asset_id() ) + existing_container_id = False try: - r = requests.get(url, verify=False) + r = requests.get(url, verify=False, timeout=DEFAULT_REQUEST_TIMEOUT) # nosemgrep resp_json = r.json() except Exception as e: err = connector._get_error_message_from_exception(e) @@ -402,7 +403,7 @@ def _update_container_for_malop_malware(self, connector, config, existing_contai update_json = container.copy() del update_json["artifacts"] url = '{0}rest/container/{1}'.format(connector.get_phantom_base_url(), existing_container_id) - r = requests.post(url, json=update_json, verify=False) + r = requests.post(url, json=update_json, verify=False, timeout=DEFAULT_REQUEST_TIMEOUT) # nosemgrep resp_json = r.json() for artifact in container["artifacts"]: @@ -435,7 +436,7 @@ def _get_artifact(self, connector, config, source_data_identifier, container_id) url = '{0}rest/artifact?_filter_source_data_identifier="{1}"&_filter_container_id={2}&sort=id&order=desc'.format( connector.get_phantom_base_url(), source_data_identifier, container_id) try: - r = requests.get(url, verify=False) + r = requests.get(url, verify=False, timeout=DEFAULT_REQUEST_TIMEOUT) # nosemgrep resp_json = r.json() except Exception as e: err = connector._get_error_message_from_exception(e) @@ -762,7 +763,7 @@ def _get_malware_with_offset(self, connector, malware_millisec_since_last_poll, def _ingest_malware(self, connector, config, malware): success = phantom.APP_ERROR container = self._get_container_dict_for_malware(connector, config, malware) - existing_container_id = self._does_container_exist_for_malop_malware(connector, malware["guid"]) + existing_container_id = self._does_container_exist_for_malop_malware(connector, "{}_{}".format(malware["guid"], malware["timestamp"])) if not existing_container_id: # Container does not exist. Go ahead and save it connector.debug_print("Saving container for Malware with id {}".format(malware["guid"])) @@ -782,7 +783,7 @@ def _get_container_dict_for_malware(self, connector, config, malware): ) container_json["data"] = malware container_json["description"] = malware["name"] - container_json["source_data_identifier"] = malware["guid"] + container_json["source_data_identifier"] = "{}_{}".format(malware["guid"], malware["timestamp"]) container_json["label"] = config.get("ingest", {}).get("container_label") status_map = self._get_status_map_malware() container_json["status"] = status_map.get(malware["status"], "New") @@ -889,7 +890,7 @@ def _get_cef_type_map(self): } # Converts timestamps from Cybereason API - # (e.g. string "1585270873770") to Phantom/ISO 8601 format (e.g. 2020-03-27T01:01:13.770Z) + # (e.g. string "1585270873770") to SOAR/ISO 8601 format (e.g. 2020-03-27T01:01:13.770Z) def _phtimestamp_from_crtimestamp(self, cybereason_timestamp): timestamp = datetime.datetime.fromtimestamp(int(cybereason_timestamp) / 1000.0) # Timestamp is in epoch-milliseconds return timestamp.isoformat()[:-3] + "Z" # Remove the microsecond accuracy, add "Z" for UTC timezone diff --git a/cybereason_query_actions.py b/cybereason_query_actions.py index b7906ea..f752442 100644 --- a/cybereason_query_actions.py +++ b/cybereason_query_actions.py @@ -14,7 +14,6 @@ import traceback -# Phantom App imports import phantom.app as phantom from phantom.action_result import ActionResult diff --git a/cybereason_session.py b/cybereason_session.py index 3bc8dfd..19596e8 100644 --- a/cybereason_session.py +++ b/cybereason_session.py @@ -14,6 +14,8 @@ import requests +from cybereason_consts import DEFAULT_REQUEST_TIMEOUT + class CybereasonSession: @@ -26,7 +28,7 @@ def __init__(self, connector): } try: url = "{0}/login.html".format(connector._base_url) - res = self.session.post(url, data=post_body, verify=connector._verify_server_cert) + res = self.session.post(url, data=post_body, verify=connector._verify_server_cert, timeout=DEFAULT_REQUEST_TIMEOUT) if self.session.cookies.get_dict().get("JSESSIONID") is None: connector.save_progress("Error when logging in to the the Cybereason console: No session cookie returned") connector.save_progress("Status code: {}".format(res.status_code)) diff --git a/logo_cybereason.svg b/logo_cybereason.svg index 02faca4..c129528 100644 --- a/logo_cybereason.svg +++ b/logo_cybereason.svg @@ -1 +1 @@ -Asset 1 \ No newline at end of file +Asset 1 diff --git a/logo_cybereason_dark.svg b/logo_cybereason_dark.svg index 157dab3..54d98ac 100644 --- a/logo_cybereason_dark.svg +++ b/logo_cybereason_dark.svg @@ -1 +1 @@ -Asset 2 \ No newline at end of file +Asset 2 diff --git a/readme.html b/readme.html index 55728ad..7a1921e 100644 --- a/readme.html +++ b/readme.html @@ -3,7 +3,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under diff --git a/readme.md b/readme.md deleted file mode 100644 index 6238497..0000000 --- a/readme.md +++ /dev/null @@ -1,32 +0,0 @@ -[comment]: # " File: readme.md" -[comment]: # "" -[comment]: # " Licensed under the Apache License, Version 2.0 (the \"License\");" -[comment]: # " you may not use this file except in compliance with the License." -[comment]: # " You may obtain a copy of the License at" -[comment]: # "" -[comment]: # " http://www.apache.org/licenses/LICENSE-2.0" -[comment]: # "" -[comment]: # " Unless required by applicable law or agreed to in writing, software distributed under" -[comment]: # " the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND," -[comment]: # " either express or implied. See the License for the specific language governing permissions" -[comment]: # " and limitations under the License." -[comment]: # "" -## Overview - -The Cybereason platform finds a single component of an attack and connects it to other pieces of -information to reveal an entire campaign and shut it down. There are two types of alerts that -Cybereason will create: - -- Malops: This stands for a Malicious Operation, and will describe machines, users, processes, and - connections used in the attack. -- Malware: These alerts are generated when a user tries to run a piece of malware. - -## Playbook Backward Compatibility - -- The below-mentioned actions have been added. Hence, it is requested to the end-user to please update their - existing playbooks by inserting | modifying | deleting the corresponding action blocks for this action on the earlier versions of the app. - - isolate specific machine - - unisolate specific machine - - upgrade sensor - - restart sensor - - query machine ip diff --git a/release_notes/2.2.0.md b/release_notes/2.2.0.md new file mode 100644 index 0000000..26e488e --- /dev/null +++ b/release_notes/2.2.0.md @@ -0,0 +1 @@ +* Minor fixes and enhancements \ No newline at end of file diff --git a/release_notes/unreleased.md b/release_notes/unreleased.md index 82a7eec..fbcb2fd 100644 --- a/release_notes/unreleased.md +++ b/release_notes/unreleased.md @@ -1 +1 @@ -**Unreleased** \ No newline at end of file +**Unreleased** diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..c4644ad --- /dev/null +++ b/tox.ini @@ -0,0 +1,7 @@ +[flake8] +max-line-length = 145 +max-complexity = 28 +extend-ignore = F403,E128,E126,E111,E121,E127,E731,E201,E202,F405,E722,D,W292 + +[isort] +line_length = 145 diff --git a/wheels/certifi-2021.10.8-py2.py3-none-any.whl b/wheels/certifi-2021.10.8-py2.py3-none-any.whl deleted file mode 100644 index fbcb86b..0000000 Binary files a/wheels/certifi-2021.10.8-py2.py3-none-any.whl and /dev/null differ diff --git a/wheels/beautifulsoup4-4.9.1-py3-none-any.whl b/wheels/py3/beautifulsoup4-4.9.1-py3-none-any.whl similarity index 100% rename from wheels/beautifulsoup4-4.9.1-py3-none-any.whl rename to wheels/py3/beautifulsoup4-4.9.1-py3-none-any.whl diff --git a/wheels/py3/certifi-2022.6.15-py3-none-any.whl b/wheels/py3/certifi-2022.6.15-py3-none-any.whl new file mode 100644 index 0000000..6e70631 Binary files /dev/null and b/wheels/py3/certifi-2022.6.15-py3-none-any.whl differ diff --git a/wheels/py3/soupsieve-2.3.2.post1-py3-none-any.whl b/wheels/py3/soupsieve-2.3.2.post1-py3-none-any.whl new file mode 100644 index 0000000..b363a9b Binary files /dev/null and b/wheels/py3/soupsieve-2.3.2.post1-py3-none-any.whl differ diff --git a/wheels/chardet-3.0.4-py2.py3-none-any.whl b/wheels/shared/chardet-3.0.4-py2.py3-none-any.whl similarity index 100% rename from wheels/chardet-3.0.4-py2.py3-none-any.whl rename to wheels/shared/chardet-3.0.4-py2.py3-none-any.whl diff --git a/wheels/idna-2.10-py2.py3-none-any.whl b/wheels/shared/idna-2.10-py2.py3-none-any.whl similarity index 100% rename from wheels/idna-2.10-py2.py3-none-any.whl rename to wheels/shared/idna-2.10-py2.py3-none-any.whl diff --git a/wheels/requests-2.25.0-py2.py3-none-any.whl b/wheels/shared/requests-2.25.0-py2.py3-none-any.whl similarity index 100% rename from wheels/requests-2.25.0-py2.py3-none-any.whl rename to wheels/shared/requests-2.25.0-py2.py3-none-any.whl diff --git a/wheels/shared/urllib3-1.26.11-py2.py3-none-any.whl b/wheels/shared/urllib3-1.26.11-py2.py3-none-any.whl new file mode 100644 index 0000000..7c66bd9 Binary files /dev/null and b/wheels/shared/urllib3-1.26.11-py2.py3-none-any.whl differ diff --git a/wheels/soupsieve-2.3-py3-none-any.whl b/wheels/soupsieve-2.3-py3-none-any.whl deleted file mode 100644 index 939d6ce..0000000 Binary files a/wheels/soupsieve-2.3-py3-none-any.whl and /dev/null differ diff --git a/wheels/urllib3-1.26.7-py2.py3-none-any.whl b/wheels/urllib3-1.26.7-py2.py3-none-any.whl deleted file mode 100644 index 62189e6..0000000 Binary files a/wheels/urllib3-1.26.7-py2.py3-none-any.whl and /dev/null differ