Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
target/
target/
mise.toml
96 changes: 96 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,99 @@ Procedure kindly provided by the PortSwigger support:
# SQLite client

Cross-platform: https://github.com/sqlitebrowser/sqlitebrowser

# Burp Suite Activity Logger - PostgreSQL Database Setup

This Docker Compose configuration sets up a PostgreSQL database for the Burp Suite Activity Logger extension.

## Quick Start

1. **Start the database:**
```bash
docker-compose up -d
```

2. **Configure your Burp Suite extension with these connection parameters:**
- **Host:** `localhost`
- **Port:** `5432`
- **Database:** `burp_activity`
- **Username:** `burp_user`
- **Password:** `burp_password`

3. **Stop the database:**
```bash
docker-compose down
```

## Services Included

### PostgreSQL Database
- **Container:** `burp-activity-db`
- **Port:** 5432 (exposed to host)
- **Database:** `burp_activity`
- **User:** `burp_user`
- **Password:** `burp_password`

### pgAdmin (Optional)
- **Container:** `burp-pgadmin`
- **Port:** 8080 (web interface)
- **Email:** `admin@example.com`
- **Password:** `admin`

To start with pgAdmin included:
```bash
docker-compose --profile admin up -d
```

## Data Persistence

Database data is stored in a Docker volume named `postgres_data`, so your data will persist between container restarts.

## Database Schema

The database automatically creates an `ACTIVITY` table with the following structure:
- `id` - Primary key (auto-increment)
- `local_source_ip` - Source IP address
- `target_url` - Target URL of the request
- `http_method` - HTTP method (GET, POST, etc.)
- `burp_tool` - Burp Suite tool that generated the request
- `request_raw` - Raw HTTP request
- `send_datetime` - When the request was sent
- `http_status_code` - HTTP response status code
- `response_raw` - Raw HTTP response
- `created_at` - When the record was created

## Performance Optimizations

The setup includes several performance optimizations:
- Indexes on commonly queried columns
- Proper user permissions
- Health checks for container monitoring

## Customization

You can modify the following in `docker-compose.yml`:
- Database name, username, and password in the `environment` section
- Port mappings if you need different ports
- Volume configurations for data storage

## Troubleshooting

1. **Connection refused errors:**
- Ensure the container is running: `docker-compose ps`
- Check container logs: `docker-compose logs postgres`

2. **Permission errors:**
- The init script sets up proper permissions automatically
- If issues persist, check the logs: `docker-compose logs postgres`

3. **Data not persisting:**
- Ensure the volume is properly created: `docker volume ls`
- Check that the container has write permissions

## Security Notes

- The default credentials are for development only
- For production use, change the default passwords
- Consider using Docker secrets for sensitive information
- Restrict network access as needed
42 changes: 42 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
version: '3.8'

services:
postgres:
image: postgres:15-alpine
container_name: burp-activity-db
environment:
POSTGRES_DB: burp_activity
POSTGRES_USER: burp_user
POSTGRES_PASSWORD: burp_password
POSTGRES_INITDB_ARGS: "--encoding=UTF8"
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U burp_user -d burp_activity"]
interval: 10s
timeout: 5s
retries: 5

# Optional: pgAdmin for database management
pgadmin:
image: dpage/pgadmin4:latest
container_name: burp-pgadmin
environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com
PGADMIN_DEFAULT_PASSWORD: admin
PGADMIN_CONFIG_SERVER_MODE: 'False'
ports:
- "8080:80"
depends_on:
- postgres
restart: unless-stopped
profiles:
- admin

volumes:
postgres_data:
driver: local
35 changes: 35 additions & 0 deletions init.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
-- Initialize the database for Burp Suite Activity Logger
-- This script runs automatically when the container starts for the first time

-- Grant necessary permissions to the burp_user
GRANT ALL PRIVILEGES ON DATABASE burp_activity TO burp_user;

-- Create the activity table (this will also be created by the Java extension, but having it here ensures it exists)
CREATE TABLE IF NOT EXISTS ACTIVITY (
id SERIAL PRIMARY KEY,
local_source_ip TEXT,
target_url TEXT,
http_method TEXT,
burp_tool TEXT,
request_raw TEXT,
send_datetime TIMESTAMP,
http_status_code TEXT,
response_raw TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Grant permissions on the table
GRANT ALL PRIVILEGES ON TABLE ACTIVITY TO burp_user;
GRANT USAGE, SELECT ON SEQUENCE activity_id_seq TO burp_user;

-- Create indexes for better performance on common queries
CREATE INDEX IF NOT EXISTS idx_activity_send_datetime ON ACTIVITY(send_datetime);
CREATE INDEX IF NOT EXISTS idx_activity_http_method ON ACTIVITY(http_method);
CREATE INDEX IF NOT EXISTS idx_activity_burp_tool ON ACTIVITY(burp_tool);
CREATE INDEX IF NOT EXISTS idx_activity_target_url ON ACTIVITY(target_url);

-- Log initialization completion
DO $$
BEGIN
RAISE NOTICE 'Burp Activity Logger database initialized successfully';
END $$;
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
<artifactId>sqlite-jdbc</artifactId>
<version>3.49.1.0</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.4</version>
</dependency>
</dependencies>

<build>
Expand Down
62 changes: 50 additions & 12 deletions src/main/java/burp/ActivityHttpListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class ActivityHttpListener implements HttpHandler {
/**
* Ref on handler that will store the activity information into the activity log storage.
*/
private ActivityLogger activityLogger;
private ActivityStorage activityStorage;

/**
* Ref on project logger.
Expand All @@ -23,22 +23,34 @@ class ActivityHttpListener implements HttpHandler {
/**
* Constructor.
*
* @param activityLogger Ref on handler that will store the activity information into the activity log storage.
* @param activityStorage Ref on handler that will store the activity information into the activity log storage.
* @param trace Ref on project logger.
*/
ActivityHttpListener(ActivityLogger activityLogger, Trace trace) {
this.activityLogger = activityLogger;
ActivityHttpListener(ActivityStorage activityStorage, Trace trace) {
this.activityStorage = activityStorage;
this.trace = trace;
}

/**
* Replace the current activity storage with a new one.
* This allows switching between storage backends without restarting Burp Suite.
*
* @param newStorage The new storage instance to use
*/
void replaceStorage(ActivityStorage newStorage) {
this.activityStorage = newStorage;
this.trace.writeLog("HTTP listener activity storage replaced.");
}

@Override
public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent requestToBeSent)
{
//Check if the response will be logged as well. If yes, wait until response is received.
if (!ConfigMenu.INCLUDE_HTTP_RESPONSE_CONTENT) {
try {
if (this.mustLogRequest(requestToBeSent)) {
this.activityLogger.logEvent(requestToBeSent, null, requestToBeSent.toolSource().toolType().toolName());
String toolName = requestToBeSent.toolSource().toolType().toolName();
if (this.mustLogRequest(requestToBeSent, toolName)) {
this.activityStorage.logEvent(requestToBeSent, null, toolName);
}
} catch (Exception e) {
this.trace.writeLog("Cannot save request: " + e.getMessage());
Expand All @@ -56,8 +68,9 @@ public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived re
if (ConfigMenu.INCLUDE_HTTP_RESPONSE_CONTENT) {
try {
//Save the information of the current request if the message is an HTTP response and according to the restriction options
if (this.mustLogRequest(responseReceived.initiatingRequest())) {
this.activityLogger.logEvent(responseReceived.initiatingRequest(), responseReceived, responseReceived.toolSource().toolType().toolName());
String toolName = responseReceived.toolSource().toolType().toolName();
if (this.mustLogRequest(responseReceived.initiatingRequest(), toolName)) {
this.activityStorage.logEvent(responseReceived.initiatingRequest(), responseReceived, toolName);
}
} catch (Exception e) {
this.trace.writeLog("Cannot save response: " + e.getMessage());
Expand All @@ -70,18 +83,40 @@ public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived re
* Determine if the current request must be logged according to the configuration options selected by the users.
*
* @param request HttpRequest object containing all the information about the request
* @param toolName Name of the tool that generated this request (e.g., "Repeater", "Intruder", "Proxy")
* @return TRUE if the request must be logged, FALSE otherwise
*/
private boolean mustLogRequest(HttpRequest request) {
private boolean mustLogRequest(HttpRequest request, String toolName) {
//By default: Request is logged
boolean mustLogRequest = true;
String url = request.url();

//this.trace.writeLog("DEBUG: Checking request from " + toolName + " to " + url);

//Initially we check the pause state
if (ConfigMenu.IS_LOGGING_PAUSED) {
mustLogRequest = false;
//this.trace.writeLog("DEBUG: Request filtered out - logging is paused");
} else {
//First: We check if we must apply restriction about image resource
if (ConfigMenu.EXCLUDE_IMAGE_RESOURCE_REQUESTS) {
//First: We check if we must apply restriction about tool source
if (ConfigMenu.FILTER_BY_TOOL_SOURCE) {
// Debug logging to see actual tool names
//this.trace.writeLog("Received tool name: '" + toolName + "', Included tools: " + ConfigMenu.INCLUDED_TOOL_SOURCES.toString());

// Check if any of the included tool sources match (case-insensitive)
boolean toolMatches = ConfigMenu.INCLUDED_TOOL_SOURCES.stream()
.anyMatch(includedTool -> includedTool.equalsIgnoreCase(toolName));

if (!toolMatches) {
mustLogRequest = false;
//this.trace.writeLog("DEBUG: Request from tool '" + toolName + "' filtered out by tool source filter.");
} else {
//this.trace.writeLog("DEBUG: Tool '" + toolName + "' passed tool source filter.");
}
}
//Second: We check if we must apply restriction about image resource
//Configuration restrictions options are applied in sequence so we only work here if the request is marked to be logged
if (mustLogRequest && ConfigMenu.EXCLUDE_IMAGE_RESOURCE_REQUESTS) {
//Get the file extension of the current URL and remove the parameters from the URL
String filename = request.url();
if (filename != null && filename.indexOf('?') != -1) {
Expand All @@ -94,16 +129,19 @@ private boolean mustLogRequest(HttpRequest request) {
String extension = filename.substring(filename.lastIndexOf('.') + 1).trim().toLowerCase(Locale.US);
if (ConfigMenu.IMAGE_RESOURCE_EXTENSIONS.contains(extension)) {
mustLogRequest = false;
//this.trace.writeLog("DEBUG: Request filtered out - image resource with extension: " + extension);
}
}
}
//Secondly: We check if we must apply restriction about the URL scope
//Finally: We check if we must apply restriction about the URL scope
//Configuration restrictions options are applied in sequence so we only work here if the request is marked to be logged
if (mustLogRequest && ConfigMenu.ONLY_INCLUDE_REQUESTS_FROM_SCOPE && ! request.isInScope()) {
mustLogRequest = false;
//this.trace.writeLog("DEBUG: Request filtered out - not in scope: " + url);
}
}

//this.trace.writeLog("DEBUG: Final decision for " + toolName + " request to " + url + ": " + (mustLogRequest ? "LOGGED" : "FILTERED"));
return mustLogRequest;

}
Expand Down
Loading