diff --git a/src/burp/gui/BinTab.java b/src/burp/gui/BinTab.java index dcb45a9..7e871a9 100644 --- a/src/burp/gui/BinTab.java +++ b/src/burp/gui/BinTab.java @@ -5,6 +5,7 @@ import burp.api.montoya.ui.editor.HttpResponseEditor; import burp.gui.ToastNotification.MessageType; import burp.models.RequestBin; +import burp.util.StorageFileUtils; import interactsh.InteractshEntry; import javax.swing.*; @@ -630,7 +631,7 @@ private List getPersistedInteractionsFromStorage() { String userHome = System.getProperty("user.home"); java.io.File storageDir = new java.io.File(userHome, ".requestbin-collaborator"); - java.io.File dataFile = new java.io.File(storageDir, "interactions-" + bin.getUniqueId() + ".json"); + java.io.File dataFile = StorageFileUtils.interactionsFile(storageDir, bin.getUniqueId()); if (!dataFile.exists()) { api.logging().logToOutput("[BinTab] No persisted interactions file for bin: " + bin.getName()); @@ -824,7 +825,7 @@ private void updateViewedStatusInStorage(java.util.Set entryKeys) { String userHome = System.getProperty("user.home"); java.io.File storageDir = new java.io.File(userHome, ".requestbin-collaborator"); - java.io.File dataFile = new java.io.File(storageDir, "interactions-" + bin.getUniqueId() + ".json"); + java.io.File dataFile = StorageFileUtils.interactionsFile(storageDir, bin.getUniqueId()); if (!dataFile.exists()) { return; @@ -999,40 +1000,57 @@ private void clearLog() { // Update empty state (switch to empty view) updateEmptyState(); - // Delete persistence file - deletePersistenceFile(); - - ToastNotification.showToast(this, "✓ Log cleared and persistence file deleted", MessageType.SUCCESS); + // Delete persistence file in background to avoid blocking EDT + deletePersistenceFileAsync(); } } /** * Delete the persistence file for this bin */ - private void deletePersistenceFile() { + private void deletePersistenceFileAsync() { + Thread deletionThread = new Thread(() -> { + boolean deleted = deletePersistenceFile(); + SwingUtilities.invokeLater(() -> { + if (deleted) { + ToastNotification.showToast(this, "✓ Log cleared and persistence file deleted", MessageType.SUCCESS); + } else { + ToastNotification.showToast(this, "✓ Log cleared (persistence file was unchanged)", MessageType.WARNING); + } + }); + }, "requestbin-delete-persistence"); + deletionThread.setDaemon(true); + deletionThread.start(); + } + + private boolean deletePersistenceFile() { try { if (bin.getUniqueId() == null || bin.getUniqueId().isEmpty()) { api.logging().logToError("Cannot delete persistence file: bin has no unique ID"); - return; + return false; } // Use same path as updateViewedStatusInStorage method String userHome = System.getProperty("user.home"); File storageDir = new File(userHome, ".requestbin-collaborator"); - File persistenceFile = new File(storageDir, "interactions-" + bin.getUniqueId() + ".json"); + File persistenceFile = StorageFileUtils.interactionsFile(storageDir, bin.getUniqueId()); if (persistenceFile.exists()) { boolean deleted = persistenceFile.delete(); if (deleted) { api.logging().logToOutput("Deleted persistence file: " + persistenceFile.getAbsolutePath()); + return true; } else { api.logging().logToError("Failed to delete persistence file: " + persistenceFile.getAbsolutePath()); + return false; } } else { api.logging().logToOutput("Persistence file does not exist: " + persistenceFile.getAbsolutePath()); + return false; } } catch (Exception e) { api.logging().logToError("Error deleting persistence file: " + e.getMessage()); + return false; } } @@ -1268,4 +1286,4 @@ public void addInteraction(Object interaction) { }); } } -} \ No newline at end of file +} diff --git a/src/burp/services/BinService.java b/src/burp/services/BinService.java index cc8982b..f922f15 100644 --- a/src/burp/services/BinService.java +++ b/src/burp/services/BinService.java @@ -4,6 +4,7 @@ import burp.models.RequestBin; import burp.models.BinServer; import burp.models.Correlation; +import burp.util.StorageFileUtils; import interactsh.InteractshEntry; import org.json.JSONArray; @@ -186,7 +187,7 @@ public boolean deleteBin(String binId) { } // delete persistence file File storageDir = new File(System.getProperty("user.home"), ".requestbin-collaborator"); - File persistenceFile = new File(storageDir, "interactions-" + toRemove.getUniqueId() + ".json"); + File persistenceFile = StorageFileUtils.interactionsFile(storageDir, toRemove.getUniqueId()); if (persistenceFile.exists()) { persistenceFile.delete(); } @@ -456,7 +457,7 @@ private void loadPersistedInteractions() { */ private int loadInteractionsForBin(RequestBin bin, java.io.File storageDir) { try { - java.io.File dataFile = new java.io.File(storageDir, "interactions-" + bin.getCorrelationId() + ".json"); + java.io.File dataFile = StorageFileUtils.interactionsFile(storageDir, bin.getCorrelationId()); if (!dataFile.exists()) { api.logging().logToOutput("[BinService] No persisted interactions for bin: " + bin.getName()); @@ -530,7 +531,7 @@ public List getPersistedInteractions(RequestBin bin) { String userHome = System.getProperty("user.home"); java.io.File storageDir = new java.io.File(userHome, ".requestbin-collaborator"); - java.io.File dataFile = new java.io.File(storageDir, "interactions-" + bin.getCorrelationId() + ".json"); + java.io.File dataFile = StorageFileUtils.interactionsFile(storageDir, bin.getCorrelationId()); if (!dataFile.exists()) { return interactions; @@ -627,4 +628,4 @@ public void clearAllBins() { } api.logging().logToOutput("Cleared all bins"); } -} \ No newline at end of file +} diff --git a/src/burp/services/PollingService.java b/src/burp/services/PollingService.java index 291fb56..26f8d3b 100644 --- a/src/burp/services/PollingService.java +++ b/src/burp/services/PollingService.java @@ -3,6 +3,7 @@ import burp.api.montoya.MontoyaApi; import burp.models.BinServer; import burp.models.Correlation; +import burp.util.StorageFileUtils; import burp.utils.CryptoUtils; import interactsh.InteractshEntry; import org.json.JSONArray; @@ -333,8 +334,8 @@ private void saveInteractionToStorage(InteractshEntry entry, Correlation correla api.logging().logToOutput("[PollingService] Created storage directory: " + storageDir.getAbsolutePath() + " (success: " + created + ")"); } - // File for this bin unique ID - File dataFile = new File(storageDir, "interactions-" + binUniqueId + ".json"); + // File for this bin unique ID (sanitized to prevent path traversal) + File dataFile = StorageFileUtils.interactionsFile(storageDir, binUniqueId); JSONArray storedInteractions; if (dataFile.exists()) { @@ -529,4 +530,4 @@ public String getBinUniqueId() { return binUniqueId; } } -} \ No newline at end of file +} diff --git a/src/burp/util/StorageFileUtils.java b/src/burp/util/StorageFileUtils.java new file mode 100644 index 0000000..3b64e94 --- /dev/null +++ b/src/burp/util/StorageFileUtils.java @@ -0,0 +1,55 @@ +package burp.util; + +import java.io.File; +import java.util.Locale; + +/** + * Utilities for safely constructing local storage file paths. + */ +public final class StorageFileUtils { + private static final int MAX_IDENTIFIER_LENGTH = 128; + private static final String DEFAULT_IDENTIFIER = "unknown"; + + private StorageFileUtils() { + // Utility class + } + + /** + * Sanitizes an identifier so it is safe to use as part of a filename. + */ + public static String sanitizeIdentifierForFilename(String identifier) { + if (identifier == null) { + return DEFAULT_IDENTIFIER; + } + + String sanitized = identifier + .trim() + .replace('\\', '_') + .replace('/', '_') + .replaceAll("[^a-zA-Z0-9._-]", "_"); + + // Avoid hidden-file style names and parent-directory patterns. + while (sanitized.startsWith(".")) { + sanitized = sanitized.substring(1); + } + sanitized = sanitized.replace("..", "_"); + + if (sanitized.isEmpty()) { + sanitized = DEFAULT_IDENTIFIER; + } + + if (sanitized.length() > MAX_IDENTIFIER_LENGTH) { + sanitized = sanitized.substring(0, MAX_IDENTIFIER_LENGTH); + } + + return sanitized.toLowerCase(Locale.ROOT); + } + + /** + * Builds the storage file path for a bin/interaction identifier. + */ + public static File interactionsFile(File storageDir, String identifier) { + String safeIdentifier = sanitizeIdentifierForFilename(identifier); + return new File(storageDir, "interactions-" + safeIdentifier + ".json"); + } +}