Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#6620 Create API importer for Bugcrowd #6621

Merged
merged 39 commits into from
Sep 14, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
64019e8
Create API importer for Bugcrowd
Gby56 Jul 28, 2022
ae5ed7a
Fix linting
Gby56 Jul 28, 2022
4a5b76f
Documentation update
Gby56 Jul 28, 2022
3dd765d
Implement URI extraction via regex, pagination fetch loop, switch to …
Gby56 Jul 28, 2022
a0596ef
Update api_client.py
damiencarol Aug 1, 2022
7cea0f2
Various fixes
Gby56 Aug 1, 2022
28ce2f9
Merge branch 'feat/bugcrowd-api' of ssh://github.com/Gby56/django-Def…
Gby56 Aug 1, 2022
954fc56
Fix dateutil parse and auth header
Gby56 Aug 1, 2022
8d5f9aa
Fix linting
Gby56 Aug 1, 2022
202e0db
Switch to session
Gby56 Aug 1, 2022
85c5021
Implement unit testing - WIP
Gby56 Aug 2, 2022
cfa24e4
Bugcrowd api importer unit tests
Gby56 Aug 2, 2022
7527975
Fix flake8
Gby56 Aug 2, 2022
e5a67a3
Simplify parameterization for bugcrowd JSONAPI format
Gby56 Aug 2, 2022
30cb8de
Fix urlencoding and loop for pagination
Gby56 Aug 2, 2022
11e9e9b
Implement generator api client
Gby56 Aug 3, 2022
b3cf2ad
v3 of fetcher with multithreading
Gby56 Aug 3, 2022
cf34267
Linting with Black, test data changed for generator function, fix tests
Gby56 Aug 12, 2022
9ec3f21
fix pep8 and add ignore W503 in flake8
Gby56 Aug 12, 2022
cccec50
remove json from test
Gby56 Aug 12, 2022
80c07f1
Use logger for endpoint parsing errors, without breaking parser
Gby56 Aug 16, 2022
1d91cbb
Strip bug url to improve endpoint parsing
Gby56 Aug 16, 2022
e4d4523
Remove regex usage
Gby56 Aug 16, 2022
36a9027
Handle endpoint uri a bit better
Gby56 Aug 16, 2022
88da487
use logger error for endpoint converting
Gby56 Aug 16, 2022
2f4cd51
Improve requests exception handling
Gby56 Aug 19, 2022
8b5b7be
Merge branch 'dev' into feat/bugcrowd-api
Gby56 Aug 19, 2022
b67f547
Merge branch 'DefectDojo:master' into feat/bugcrowd-api
Gby56 Sep 13, 2022
7fd56b5
Remove regexes, convert_endpoint function
Gby56 Sep 13, 2022
fa9f306
Raise exeptions for responses and connection tests
Gby56 Sep 13, 2022
4811405
Do not save broken endpoints, add cleaning in tests
Gby56 Sep 13, 2022
8fa151b
Align to dev branch
Gby56 Sep 13, 2022
b988ee3
Named ValidationError exceptions
Gby56 Sep 13, 2022
b95ebe6
Fix conflicts
Gby56 Sep 13, 2022
016e153
Fix conflicts
Gby56 Sep 13, 2022
26459da
Add response text in error message
Gby56 Sep 14, 2022
59e4b53
Fix liniting
Gby56 Sep 14, 2022
98c7522
Merge branch 'dev' into feat/bugcrowd-api
Gby56 Sep 14, 2022
41875e3
Update __init__.py
Gby56 Sep 14, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/content/en/integrations/parsers.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,18 @@ Import Brakeman Scanner findings in JSON format.

Import Bugcrowd results in CSV format.

### Bugcrowd API

Import Bugcrowd submissions directly from the API using the API token.
Set your API key directly in the format `username:password` in the API Token input, it will be added to the header `'Authorization': 'Token {}'.format(self.api_token),`
For each product, you can configure 2 things:
- Service key 1: the bugcrowd program code (it's the slug name in the url for the program, url safe)
- Service key 2: the bugcrowd target name (the full name, it will be url-encoded, you can find it in https://tracker.bugcrowd.com/<YOURPROGRAM>/settings/scope/target_groups)
- It can be left empty so that all program submissions are imported

That way, per product, you can use the same program but separate by target, which is a fairly common way of filtering/grouping Bugcrowd.
Adding support for a 3rd filtering would be possible with Service Key 3, feel free to make a PR.

### Bundler-Audit

Import the text output generated with bundle-audit check
Expand Down
3 changes: 3 additions & 0 deletions dojo/settings/settings.dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -1112,6 +1112,7 @@ def saml2_attrib_map_format(dict):
'Solar Appscreener Scan': ['title', 'file_path', 'line', 'severity'],
'pip-audit Scan': ['vuln_id_from_tool', 'component_name', 'component_version'],
'Edgescan Scan': ['unique_id_from_tool'],
'Bugcrowd API': ['unique_id_from_tool'],
'Rubocop Scan': ['vuln_id_from_tool', 'file_path', 'line'],
'JFrog Xray Scan': ['title', 'description', 'component_name', 'component_version'],
'CycloneDX Scan': ['vuln_id_from_tool', 'component_name', 'component_version'],
Expand Down Expand Up @@ -1155,6 +1156,7 @@ def saml2_attrib_map_format(dict):
'Semgrep JSON Report': True,
'Generic Findings Import': True,
'Edgescan Scan': True,
'Bugcrowd API': True,
}

# List of fields that are known to be usable in hash_code computation)
Expand Down Expand Up @@ -1257,6 +1259,7 @@ def saml2_attrib_map_format(dict):
'Gitleaks Scan': DEDUPE_ALGO_HASH_CODE,
'pip-audit Scan': DEDUPE_ALGO_HASH_CODE,
'Edgescan Scan': DEDUPE_ALGO_HASH_CODE,
'Bugcrowd API': DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL,
'Rubocop Scan': DEDUPE_ALGO_HASH_CODE,
'JFrog Xray Scan': DEDUPE_ALGO_HASH_CODE,
'CycloneDX Scan': DEDUPE_ALGO_HASH_CODE,
Expand Down
2 changes: 2 additions & 0 deletions dojo/templates/dojo/add_product_api_scan_configuration.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ <h3> Add {{ product.name }} API Scan Configuration</h3>
API Scan Configurations are supported for the test types SonarQube API, Cobalt.io API and Edgescan API.
<ul>
<li>For <b>SonarQube API</b> the field <b>Service key 1</b> has to be set with the SonarQube project key.</li>
<li>For <b>Bugcrowd API</b> the field <b>Service key 1</b> has to be set with the Bugcrowd program code.
<b>Service key 2</b> can be set with the target in the Bugcrowd program (will be url encoded for the api call), if not supplied, will fetch all submissions in the program</li>
<li>For <b>Cobalt.io API</b> the field <b>Service key 1</b> has to be set with the Cobalt.io asset id.
<b>Service key 2</b> will be populated with the asset name while saving the configuration.</li>
<li>For <b>Edgescan API</b> the field <b>Service key 1</b> has to be set with the Edgescan asset id.</li>
Expand Down
2 changes: 2 additions & 0 deletions dojo/templates/dojo/edit_product_api_scan_configuration.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ <h3>Edit API Scan Configuration</h3>
API Scan Configurations are supported for the test types SonarQube API, Cobalt.io API and Edgescan API.
<ul>
<li>For <b>SonarQube API</b> the field <b>Service key 1</b> has to be set with the SonarQube project key.</li>
<li>For <b>Bugcrowd API</b> the field <b>Service key 1</b> has to be set with the Bugcrowd program code.
<b>Service key 2</b> can be set with the target in the Bugcrowd program (will be url encoded for the api call), if not supplied, will fetch all submissions in the program</li>
<li>For <b>Cobalt.io API</b> the field <b>Service key 1</b> has to be set with the Cobalt.io asset id.
<b>Service key 2</b> will be populated with the asset name while saving the configuration.</li>
<li>For <b>Edgescan API</b> the field <b>Service key 1</b> has to be set with the Edgescan asset id.</li>
Expand Down
5 changes: 4 additions & 1 deletion dojo/tool_config/factory.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from dojo.tools.sonarqube_api.api_client import SonarQubeAPI
from dojo.tools.cobalt_api.api_client import CobaltAPI
from dojo.tools.edgescan.api_client import EdgescanAPI
from dojo.tools.bugcrowd_api.api_client import BugcrowdAPI

SCAN_APIS = {'SonarQube': SonarQubeAPI,
'Cobalt.io': CobaltAPI,
'Edgescan API': EdgescanAPI}
'Edgescan API': EdgescanAPI,
'Bugcrowd API': BugcrowdAPI
}


def create_API(tool_configuration):
Expand Down
Empty file.
104 changes: 104 additions & 0 deletions dojo/tools/bugcrowd_api/api_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import requests
from urllib.parse import quote

from dojo.models import Tool_Type


class BugcrowdAPI:
"""
A simple client for the bugcrowd.io API
"""

bugcrowd_api_url = "https://api.bugcrowd.com"

def __init__(self, tool_config):
tool_type, _ = Tool_Type.objects.get_or_create(name='Bugcrowd API')
damiencarol marked this conversation as resolved.
Show resolved Hide resolved

self.session = requests.Session()
if tool_config.authentication_type == "API":
self.api_token = tool_config.api_key
else:
raise Exception('bugcrowd Authentication type {} not supported'.format(tool_config.authentication_type))

def get_findings(self, program, target):
"""
Returns the findings for a given bugcrowd program and target, if target is *, everything is returned
:param program:
:param target:
:return:
"""
output = {'data':[]}

if target:
next = '{}/submissions?filter%5Bprogram%5D={}&filter%5Btarget%5D={}&page%5Blimit%5D=100&page%5Boffset%5D=0&include=monetary_rewards,target&filter%5Bduplicate%5D=false&sort=submitted-desc'.format(self.bugcrowd_api_url, program, quote(target))
else:
next = '{}/submissions?filter%5Bprogram%5D={}&page%5Blimit%5D=100&page%5Boffset%5D=0&include=monetary_rewards,target&filter%5Bduplicate%5D=false&sort=submitted-desc'.format(self.bugcrowd_api_url, program)
Gby56 marked this conversation as resolved.
Show resolved Hide resolved

while next != '':
if target:
response = self.session.get(
url=next,
headers=self.get_headers(),
)
else:
response = self.session.get(
url=next,
headers=self.get_headers(),
)

if response.ok:
data = response.json()
if len(data['data']) == 0 or data['meta']['total_hits'] == data['meta']['count']:
break

print('Fetched ' + str(len(data['data'])) + ' submissions')
output['data'] = output['data'] + data['data']
next='{}{}'.format(self.bugcrowd_api_url,data["links"]["next"])
else:
next='over'
raise Exception("Unable to get asset findings due to {} - {}".format(
response.status_code, response.content.decode("utf-8")
))

print('Total gathered submissions from Bugcrowd: ' + str(len(output['data'])))

return output


def test_connection(self):
# Request programs
response_programs = self.session.get(
url='{}/programs'.format(self.bugcrowd_api_url),
headers=self.get_headers(),
)

# Request assets to validate the org token
response_subs = self.session.get(
url='{}/submissions'.format(self.bugcrowd_api_url),
headers=self.get_headers(),
)

if response_programs.ok and response_subs.ok:
Gby56 marked this conversation as resolved.
Show resolved Hide resolved
data = response_programs.json().get('data')
progs = list(filter(lambda prog: prog["type"] == "program", data))
program_names = ', '.join(list(map(lambda p: p["attributes"]["name"], progs)))
return f'You have access to the "{ program_names }" programs'
else:
raise Exception("Connection failed (error: {} - {})".format(
response_assets.status_code, response_assets.content.decode("utf-8")
))

def test_product_connection(self, api_scan_configuration):
submissions = self.get_findings(api_scan_configuration.service_key_1, api_scan_configuration.service_key_2)
submission_number = len(submissions['data'])
return f'You have access to "{submission_number}" submissions in Bugcrowd in the Program code "{api_scan_configuration.service_key_1}" and Target "{api_scan_configuration.service_key_2}" (leave service key 2 empty to get all submissions in program)'

def get_headers(self):
headers = {
'Accept': 'application/vnd.bugcrowd+json',
'Authorization': 'Token {}'.format(self.api_token),
'User-Agent': 'DefectDojo',
'Bugcrowd-Version': '2021-10-28'
}

return headers
Gby56 marked this conversation as resolved.
Show resolved Hide resolved
41 changes: 41 additions & 0 deletions dojo/tools/bugcrowd_api/importer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import logging
from django.core.exceptions import ValidationError
from dojo.models import Product_API_Scan_Configuration
from dojo.tools.bugcrowd_api.api_client import BugcrowdAPI

logger = logging.getLogger(__name__)


class BugcrowdApiImporter(object):
"""
Import from Bugcrowd API
"""

def get_findings(self, test):
client, config = self.prepare_client(test)
logger.info("Fetching submissions program " + str(config.service_key_1) + " and target " + str(config.service_key_2))
Gby56 marked this conversation as resolved.
Show resolved Hide resolved
findings = client.get_findings(config.service_key_1, config.service_key_2)
return findings

def prepare_client(self, test):
product = test.engagement.product
if test.api_scan_configuration:
config = test.api_scan_configuration
# Double check of config
if config.product != product:
raise ValidationError('API Scan Configuration for Bugcrowd and Product do not match.')
else:
configs = Product_API_Scan_Configuration.objects.filter(product=product, tool_configuration__tool_type__name='Bugcrowd API')
if configs.count() == 1:
config = configs.first()
elif configs.count() > 1:
raise ValidationError(
'More than one Product API Scan Configuration has been configured, but none of them has been chosen. Please specify at Test which one should be used.'
)
else:
raise ValidationError(
'There are no API Scan Configurations for this Product. Please add at least one API Scan Configuration for bugcrowd to this Product.'
)

tool_config = config.tool_configuration
return BugcrowdAPI(tool_config), config
Loading