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

Issue #25 Added ability to get the report filename #26

Merged
merged 12 commits into from
Sep 27, 2023
11 changes: 7 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: test

on:
push:
branches: '*'
branches: [ main ]
pull_request:
branches: [ master ]
branches: [ main ]

jobs:
test:
Expand All @@ -29,5 +29,8 @@ jobs:
python -m pip install wheel tox
python setup.py install

- name: Run tests
run: tox -p all
- name: Run mypy
run: tox -e mypy

- name: Run pytype
run: tox -e pytype
2 changes: 1 addition & 1 deletion burpa/__version__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__author__ = 'Adel "0x4d31" Karimi, and other contributors'
__version__ = '0.3.9'
__version__ = '0.3.10'
114 changes: 82 additions & 32 deletions burpa/_burpa.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,14 @@
@attr.s(auto_attribs=True)
class ScanRecord:
"""
Temporary record represents a running scan.
Model a scan, running or finished.
"""
task_id: str
task_id: str
target_url: str
date_time: str
status: Optional[str] = None
metrics: Dict[str, Any] = attr.ib(factory=dict)
report_files: Optional[List[str]] = None

@property
def name(self) -> str:
Expand Down Expand Up @@ -276,7 +277,7 @@ def scan(self, *targets: str, report_type: str = "HTML",
app_user: str = "",
app_pass: str = "",
issue_severity:Union[str, Tuple[str, ...]]="All",
issue_confidence:Union[str, Tuple[str, ...]]="All", csv:bool=False) -> None:
issue_confidence:Union[str, Tuple[str, ...]]="All", csv:bool=False) -> List[ScanRecord]:
"""
Launch an active scan, wait until the end and report the results.

Expand Down Expand Up @@ -312,6 +313,11 @@ def scan(self, *targets: str, report_type: str = "HTML",
Multiple values are also accepted if they are comma-separated.
csv:
Whether to generate a CSV summary with all issues.

Returns
-------
list of ScanRecord
list of scan records
"""

self._test()
Expand All @@ -326,34 +332,39 @@ def scan(self, *targets: str, report_type: str = "HTML",

self._scan_metrics(*records)

# Download the scan issues/reports
if report_type.lower() != 'none':
self.report(*(r.target_url for r in records), report_type=report_type,
report_output_dir=report_output_dir,
issue_severity=issue_severity,
issue_confidence=issue_confidence,
csv=csv, )

reportError = None
for record in records:

# Download the scan issues/reports
if report_type.lower() != 'none':
record.report_files = self.report(record.target_url, report_type=report_type,
report_output_dir=report_output_dir,
issue_severity=issue_severity,
issue_confidence=issue_confidence,
csv=csv, )

# Raise error if a scan failed
caption = record.metrics['crawl_and_audit_caption']
if record.status == "paused":
raise BurpaError(f"Scan aborted - {record.target_url} : {caption}")
reportError = f"Scan aborted - {record.target_url} : {caption}"
elif record.status == "failed":
raise BurpaError(f"Scan failed - {record.target_url} : {caption}")
reportError = f"Scan failed - {record.target_url} : {caption}"

def _report(self, target: str, report_type: str,
report_output_dir: Optional[str] = None,
if reportError:
raise BurpaError(reportError, records=records)

return records

def _report(self, target: str,
report_type: str,
timestamp: str,
report_output_dir: str,
issue_severity:Union[str, Tuple[str, ...]]="All",
issue_confidence:Union[str, Tuple[str, ...]]="All",
csv:bool=False) -> None:

csv:bool=False) -> List[str]:

file_names: List[str] = []
issues = self._api.scan_issues(target)

# Use the same datetime string for the CSV report and the HTMl report
report_file_datetime_string = time.strftime("%Y%m%d-%H%M", time.localtime())

if report_output_dir:
os.makedirs(report_output_dir, exist_ok=True)

Expand All @@ -374,11 +385,12 @@ def _report(self, target: str, report_type: str,
filename_template = "burp-report_{}_{}.{}"

# Write the response body (byte array) to file
report_file_name = get_valid_filename(filename_template.format(
report_file_datetime_string, target, report_type.lower()))
report_files = get_valid_filename(filename_template.format(
timestamp, target, report_type.lower()))

csv_file = os.path.join(report_output_dir or tempfile.gettempdir(), report_file_name)
with open(csv_file, 'w', encoding='utf8') as output_file:
report_file = os.path.join(report_output_dir or tempfile.gettempdir(), report_files)
file_names.append(report_file)
with open(report_file, 'w', encoding='utf8') as output_file:

self._api.write_report(
report_type=report_type,
Expand All @@ -390,33 +402,71 @@ def _report(self, target: str, report_type: str,

else:
self._logger.info(f"No issue could be found for the target {target}")
issues = []
return []

if csv:
# Generate a CSV file with issues
csv_file_name = get_valid_filename("burp-report-summary_{}_{}.csv".format(
report_file_datetime_string, target))
timestamp, target))

csv_file = os.path.join(report_output_dir or tempfile.gettempdir(), csv_file_name)
file_names.append(csv_file)

with open(csv_file, 'w', encoding='utf8') as output_file:
generate_csv(output_file, issues, report_datetime=report_file_datetime_string)
generate_csv(output_file, issues, report_datetime=timestamp)
self._logger.info(f'Generated CSV file at {csv_file}')

return file_names

def report(self, *targets: str, report_type: str = "HTML",
report_output_dir: str = "",
issue_severity: Union[str, Tuple[str, ...]]="All",
issue_confidence: Union[str, Tuple[str, ...]]="All",
csv: bool=False) -> None:
csv: bool=False) -> List[str]:
"""
Generate the reports for the specified targets URLs.
If targets is 'all', generate a report that contains all issues for all targets.
If targets is 'all', generate reports that contains all issues for all targets.

Args
----
targets:
Target URL(s) or filename to load target URL(s) from.
Use 'all' keyword to search in the proxy history and
load target URLs from there.
report_type:
Burp scan report type (default: HTML).
Use 'none' to skip reporting.
report_output_dir:
Directory to store the reports.
Store report in temp directory if empty.
issue_severity:
Severity of the scan issues to be included in the report. Acceptable values are All, High, Medium, Low and Information.
Multiple values are also accepted if they are comma-separated.
issue_confidence:
Confidence of the scan issues to be included in the report. Acceptable values are All, Certain, Firm and Tentative.
Multiple values are also accepted if they are comma-separated.
csv:
Whether to generate a CSV summary with all issues.

Returns
-------
list of str
list of generated report files
"""
self._test()

file_names = []
# Use the same datetime string for the CSV report and the HTMl report
timestamp = time.strftime("%Y%m%d-%H%M", time.localtime())

for target in targets:
self._report(target, report_type, report_output_dir, issue_severity=issue_severity,
issue_confidence=issue_confidence, csv=csv)
file_names += self._report(target, report_type, timestamp=timestamp,
report_output_dir=report_output_dir,
issue_severity=issue_severity,
issue_confidence=issue_confidence,
csv=csv)

return file_names

def proxy_listen_all_interfaces(self, proxy_port: str) -> None:
"""
Expand Down
12 changes: 12 additions & 0 deletions burpa/_error.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
from typing import TYPE_CHECKING, Optional, List

if TYPE_CHECKING:
from ._burpa import ScanRecord

class BurpaError(Exception):
"""
Exception raised when there is an error in a burpa command.
"""
def __init__(self, msg: str, records:Optional[List['ScanRecord']]=None) -> None:
super().__init__(msg)
self.records = records
"""
If the burp scan ended in a failed state, this attribute will old
a list of scan records. They can still contain some valuable informations.
"""
Loading