Skip to content

Commit 4d30d4c

Browse files
feat: persistent findings storage
1 parent b2a031c commit 4d30d4c

File tree

8 files changed

+176
-10
lines changed

8 files changed

+176
-10
lines changed

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Experimenting with yarrr' Burp Proxy tab going brrrrrrrrrrrrr.
2323

2424
- [burpference](#burpference)
2525
- [Prerequisites](#prerequisites)
26+
- [Project Structure](#project-structure)
2627
- [Setup Guide](#setup-guide)
2728
- [1. Download and Install Burp Suite](#1-download-and-install-burp-suite)
2829
- [2. Download and import Jython standalone JAR file](#2-download-and-import-jython-standalone-jar-file)
@@ -69,7 +70,25 @@ Some key features:
6970
- Injection vulnerability assessment across all vectors
7071
- Additional templates can be created for specific testing scenarios
7172
- Dynamic prompt switching during runtime to tailor analysis based on target endpoints
73+
- **Persistent Findings Storage**: All security findings are automatically stored and tracked:
74+
- Findings are saved to `logs/findings.json` in a structured format (example below)
75+
- Each finding includes timestamp, severity, details, and affected URLs and persist across Burp Suite sessions
76+
- Findings are synchronized between Scanner and Proxy analysis
77+
78+
```json
79+
[
80+
{
81+
"detail": "{u'<FINDING>'model': u'<provider>/<model>'}",
82+
"host": "www.evil.io",
83+
"name": "burpference: <SEVERITY> Security Finding",
84+
"severity": "<SEVERITY>",
85+
"timestamp": "2025-02-09T19:35:56.667000",
86+
"url": "https://www.evil.io:443/"
87+
}
88+
]
89+
```
7290

91+
<br>
7392
So grab yer compass, hoist the mainsail, and let **burpference** be yer guide as ye plunder the seven seas of HTTP traffic! Yarrr'!
7493

7594
---
@@ -98,6 +117,32 @@ Before using **Burpference**, ensure you have the following:
98117

99118
---
100119

120+
## Project Structure
121+
```
122+
/Users/ads/git/burpference/
123+
├── burpference/ # Main package directory
124+
│ ├── __init__.py # Package initialization
125+
│ ├── api_adapters.py # API providers and adapters
126+
│ ├── assets/ # Internal assets
127+
│ │ └── squid_ascii.txt # ASCII art for extension
128+
│ ├── burpference.py # Main extension code
129+
│ ├── consts.py # Constants and configurations
130+
│ ├── db_manager.py # Database operations
131+
│ ├── issues.py # Burp issue implementations
132+
│ └── scanner.py # Security scanner functionality
133+
├── configs/ # API configuration files
134+
│ └── *.json # JSON config files per provider
135+
├── logs/ # Log output directory
136+
│ ├── findings.json # Security findings database
137+
│ └── *.txt # Generated log files
138+
├── prompts/ # Prompt template files
139+
│ ├── proxy_prompt.txt # Default proxy analysis prompt
140+
│ ├── scanner_prompt.txt # Scanner analysis prompt
141+
│ └── openapi_prompt.txt # OpenAPI analysis prompt
142+
├── LICENSE # Project license
143+
└── README.md # Project documentation
144+
```
145+
101146
## Setup Guide
102147

103148
### 1. Download and Install Burp Suite

burpference/__init__.py

Whitespace-only changes.

burpference/burpference.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from consts import *
1818
from api_adapters import get_api_adapter
1919
from issues import BurpferenceIssue
20+
from db_manager import BurpDBManager
2021
from threading import Thread
2122
from scanner import BurpferenceScanner
2223

@@ -56,6 +57,7 @@ def __init__(self):
5657
self.log_message("Extension initialized and running.")
5758
self._hosts = set()
5859
self.scanner = None # Will initialize after callbacks
60+
self.db_manager = BurpDBManager()
5961

6062
def registerExtenderCallbacks(self, callbacks):
6163
self._callbacks = callbacks
@@ -838,7 +840,6 @@ def create_scan_issue(self, messageInfo, processed_response):
838840
severity = self.extract_severity_from_response(processed_response)
839841
burp_severity = self.map_severity(severity)
840842

841-
# Convert response to string and handle escaping
842843
if isinstance(processed_response, str):
843844
detail = processed_response
844845
else:
@@ -847,7 +848,6 @@ def create_scan_issue(self, messageInfo, processed_response):
847848
if detail.startswith('"') and detail.endswith('"'):
848849
detail = detail[1:-1]
849850

850-
# Create properly formatted issue name
851851
issue_name = "burpference: %s Security Finding" % severity
852852

853853
issue = BurpferenceIssue(
@@ -860,6 +860,17 @@ def create_scan_issue(self, messageInfo, processed_response):
860860
confidence="Certain"
861861
)
862862

863+
finding_dict = {
864+
"timestamp": datetime.now().isoformat(),
865+
"name": issue_name,
866+
"severity": burp_severity,
867+
"detail": detail,
868+
"url": str(issue.getUrl()),
869+
"host": messageInfo.getHttpService().getHost()
870+
}
871+
self.db_manager.add_finding(finding_dict)
872+
self.log_message("Saved finding to database")
873+
863874
self._callbacks.addScanIssue(issue)
864875
self.log_message("Added %s issue to Burp Scanner" % severity)
865876

burpference/consts.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import os
22
from java.awt import Color
33

4-
# Get the directory containing burpference.py
5-
EXTENSION_DIR = os.path.dirname(os.path.abspath(__file__))
6-
ROOT_DIR = os.path.dirname(EXTENSION_DIR) # Parent directory of burpference folder
7-
# Define paths relative to ROOT_DIR
8-
CONFIG_DIR = os.path.join(ROOT_DIR, "configs")
9-
LOG_DIR = os.path.join(ROOT_DIR, "logs")
10-
PROMPTS_DIR = os.path.join(ROOT_DIR, "prompts")
4+
BURPFERENCE_DIR = os.path.dirname(
5+
os.path.abspath(__file__)
6+
) # /path/to/burpference/burpference
7+
PROJECT_ROOT = os.path.dirname(BURPFERENCE_DIR) # /path/to/burpference
8+
9+
CONFIG_DIR = os.path.join(PROJECT_ROOT, "configs") # /path/to/burpference/configs
10+
LOG_DIR = os.path.join(PROJECT_ROOT, "logs") # /path/to/burpference/logs
11+
PROMPTS_DIR = os.path.join(PROJECT_ROOT, "prompts") # /path/to/burpference/prompts
1112
PROXY_PROMPT = os.path.join(PROMPTS_DIR, "proxy_prompt.txt")
12-
SQUID_ASCII_FILE = os.path.join(ROOT_DIR, "assets", "squid_ascii.txt")
13+
SQUID_ASCII_FILE = os.path.join(BURPFERENCE_DIR, "assets", "squid_ascii.txt")
1314

1415
# Color constants
1516
DREADNODE_ORANGE = Color(255, 140, 0)

burpference/db_manager.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import os
2+
import json
3+
from datetime import datetime
4+
from consts import PROJECT_ROOT, LOG_DIR
5+
6+
7+
class BurpDBManager:
8+
def __init__(self, db_path=None):
9+
if db_path is None:
10+
db_path = os.path.join(LOG_DIR, "findings.json")
11+
self.db_path = db_path
12+
print("[*] DB Path: %s" % self.db_path)
13+
self._ensure_db_dir()
14+
self._findings = self._load_findings()
15+
print("[*] Loaded %d findings" % len(self._findings))
16+
17+
def _ensure_db_dir(self):
18+
"""Ensure the database directory exists"""
19+
db_dir = os.path.dirname(self.db_path)
20+
if not os.path.exists(db_dir):
21+
os.makedirs(db_dir)
22+
23+
def _load_findings(self):
24+
"""Load findings from JSON file"""
25+
if os.path.exists(self.db_path):
26+
try:
27+
with open(self.db_path, "r") as f:
28+
return json.load(f)
29+
except Exception as e:
30+
print("[!] Error loading findings: %s" % str(e))
31+
return []
32+
return []
33+
34+
def _save_findings(self):
35+
"""Save findings to JSON file"""
36+
try:
37+
with open(self.db_path, "w") as f:
38+
json.dump(self._findings, f, indent=2)
39+
print("[+] Saved %d findings to %s" % (len(self._findings), self.db_path))
40+
except Exception as e:
41+
print("[!] Error saving findings: %s" % str(e))
42+
43+
def add_finding(self, finding):
44+
"""Add a new finding"""
45+
finding["timestamp"] = datetime.now().isoformat()
46+
self._findings.append(finding)
47+
self._save_findings()
48+
49+
def get_findings(self, filters=None):
50+
"""Retrieve findings with optional filters"""
51+
if not filters:
52+
return self._findings
53+
54+
filtered = []
55+
for finding in self._findings:
56+
matches = True
57+
for key, value in filters.items():
58+
if key not in finding or finding[key] != value:
59+
matches = False
60+
break
61+
if matches:
62+
filtered.append(finding)
63+
64+
return filtered

burpference/issues.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from burp import IScanIssue
2+
import json
23

34

45
class BurpferenceIssue(IScanIssue):
@@ -45,3 +46,23 @@ def getHttpMessages(self):
4546

4647
def getHttpService(self):
4748
return self._httpService
49+
50+
def to_dict(self):
51+
"""Convert issue to dictionary for database storage"""
52+
return {
53+
"name": self._name,
54+
"detail": self._detail,
55+
"severity": self._severity,
56+
"confidence": self._confidence,
57+
"host": self._httpService.getHost(),
58+
"url": str(self._url),
59+
"request_response": json.dumps(
60+
[
61+
{
62+
"request": msg.getRequest().tostring(),
63+
"response": msg.getResponse().tostring(),
64+
}
65+
for msg in self._httpMessages
66+
]
67+
),
68+
}

burpference/scanner.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import re
2121
from datetime import datetime
2222
from javax.swing.border import EmptyBorder
23+
from db_manager import BurpDBManager
24+
from issues import BurpferenceIssue
2325

2426
SCANNER_PROMPT = os.path.join(
2527
os.path.dirname(os.path.dirname(__file__)), "prompts", "scanner_prompt.txt"
@@ -44,6 +46,7 @@ def __init__(self, callbacks, helpers, config, api_adapter, colors=None):
4446
self.LIGHTER_BACKGROUND = self.colors.get("LIGHTER_BACKGROUND")
4547
self.DREADNODE_GREY = self.colors.get("DREADNODE_GREY")
4648
self.DREADNODE_ORANGE = self.colors.get("DREADNODE_ORANGE")
49+
self.db_manager = BurpDBManager()
4750

4851
def add_host(self, host):
4952
"""Add a host to the scanner's tracked hosts"""
@@ -503,3 +506,24 @@ def update_config_display(self):
503506
if hasattr(self, "config_label"):
504507
self.config_label.setText(self.get_config_status())
505508
self.refresh_prompt_template()
509+
510+
def create_scan_issue(self, messageInfo, processed_response):
511+
try:
512+
issue = BurpferenceIssue(
513+
httpService=self._helpers.buildHttpService(
514+
host, port, protocol == "https"
515+
),
516+
url=url,
517+
httpMessages=[messageInfo],
518+
name="burpference Scanner: %s Finding" % level,
519+
detail=analysis,
520+
severity=severity,
521+
confidence="Certain",
522+
)
523+
524+
self.db_manager.add_finding(issue.to_dict())
525+
526+
self._callbacks.addScanIssue(issue)
527+
528+
except Exception as e:
529+
self._callbacks.printOutput("Error creating scan issue: %s" % str(e))

0 commit comments

Comments
 (0)