Skip to content

Commit f191c09

Browse files
authored
Merge pull request #1 from requestbin/codex/fix-path-traversal-vulnerability-and-ui-freeze
Prevent path traversal in interaction storage and make Clear All deletion async
2 parents 47e8855 + 18fb8c8 commit f191c09

4 files changed

Lines changed: 92 additions & 17 deletions

File tree

src/burp/gui/BinTab.java

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import burp.api.montoya.ui.editor.HttpResponseEditor;
66
import burp.gui.ToastNotification.MessageType;
77
import burp.models.RequestBin;
8+
import burp.util.StorageFileUtils;
89
import interactsh.InteractshEntry;
910

1011
import javax.swing.*;
@@ -630,7 +631,7 @@ private List<InteractshEntry> getPersistedInteractionsFromStorage() {
630631

631632
String userHome = System.getProperty("user.home");
632633
java.io.File storageDir = new java.io.File(userHome, ".requestbin-collaborator");
633-
java.io.File dataFile = new java.io.File(storageDir, "interactions-" + bin.getUniqueId() + ".json");
634+
java.io.File dataFile = StorageFileUtils.interactionsFile(storageDir, bin.getUniqueId());
634635

635636
if (!dataFile.exists()) {
636637
api.logging().logToOutput("[BinTab] No persisted interactions file for bin: " + bin.getName());
@@ -824,7 +825,7 @@ private void updateViewedStatusInStorage(java.util.Set<String> entryKeys) {
824825

825826
String userHome = System.getProperty("user.home");
826827
java.io.File storageDir = new java.io.File(userHome, ".requestbin-collaborator");
827-
java.io.File dataFile = new java.io.File(storageDir, "interactions-" + bin.getUniqueId() + ".json");
828+
java.io.File dataFile = StorageFileUtils.interactionsFile(storageDir, bin.getUniqueId());
828829

829830
if (!dataFile.exists()) {
830831
return;
@@ -999,40 +1000,57 @@ private void clearLog() {
9991000
// Update empty state (switch to empty view)
10001001
updateEmptyState();
10011002

1002-
// Delete persistence file
1003-
deletePersistenceFile();
1004-
1005-
ToastNotification.showToast(this, "✓ Log cleared and persistence file deleted", MessageType.SUCCESS);
1003+
// Delete persistence file in background to avoid blocking EDT
1004+
deletePersistenceFileAsync();
10061005
}
10071006
}
10081007

10091008
/**
10101009
* Delete the persistence file for this bin
10111010
*/
1012-
private void deletePersistenceFile() {
1011+
private void deletePersistenceFileAsync() {
1012+
Thread deletionThread = new Thread(() -> {
1013+
boolean deleted = deletePersistenceFile();
1014+
SwingUtilities.invokeLater(() -> {
1015+
if (deleted) {
1016+
ToastNotification.showToast(this, "✓ Log cleared and persistence file deleted", MessageType.SUCCESS);
1017+
} else {
1018+
ToastNotification.showToast(this, "✓ Log cleared (persistence file was unchanged)", MessageType.WARNING);
1019+
}
1020+
});
1021+
}, "requestbin-delete-persistence");
1022+
deletionThread.setDaemon(true);
1023+
deletionThread.start();
1024+
}
1025+
1026+
private boolean deletePersistenceFile() {
10131027
try {
10141028
if (bin.getUniqueId() == null || bin.getUniqueId().isEmpty()) {
10151029
api.logging().logToError("Cannot delete persistence file: bin has no unique ID");
1016-
return;
1030+
return false;
10171031
}
10181032

10191033
// Use same path as updateViewedStatusInStorage method
10201034
String userHome = System.getProperty("user.home");
10211035
File storageDir = new File(userHome, ".requestbin-collaborator");
1022-
File persistenceFile = new File(storageDir, "interactions-" + bin.getUniqueId() + ".json");
1036+
File persistenceFile = StorageFileUtils.interactionsFile(storageDir, bin.getUniqueId());
10231037

10241038
if (persistenceFile.exists()) {
10251039
boolean deleted = persistenceFile.delete();
10261040
if (deleted) {
10271041
api.logging().logToOutput("Deleted persistence file: " + persistenceFile.getAbsolutePath());
1042+
return true;
10281043
} else {
10291044
api.logging().logToError("Failed to delete persistence file: " + persistenceFile.getAbsolutePath());
1045+
return false;
10301046
}
10311047
} else {
10321048
api.logging().logToOutput("Persistence file does not exist: " + persistenceFile.getAbsolutePath());
1049+
return false;
10331050
}
10341051
} catch (Exception e) {
10351052
api.logging().logToError("Error deleting persistence file: " + e.getMessage());
1053+
return false;
10361054
}
10371055
}
10381056

@@ -1268,4 +1286,4 @@ public void addInteraction(Object interaction) {
12681286
});
12691287
}
12701288
}
1271-
}
1289+
}

src/burp/services/BinService.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import burp.models.RequestBin;
55
import burp.models.BinServer;
66
import burp.models.Correlation;
7+
import burp.util.StorageFileUtils;
78

89
import interactsh.InteractshEntry;
910
import org.json.JSONArray;
@@ -186,7 +187,7 @@ public boolean deleteBin(String binId) {
186187
}
187188
// delete persistence file
188189
File storageDir = new File(System.getProperty("user.home"), ".requestbin-collaborator");
189-
File persistenceFile = new File(storageDir, "interactions-" + toRemove.getUniqueId() + ".json");
190+
File persistenceFile = StorageFileUtils.interactionsFile(storageDir, toRemove.getUniqueId());
190191
if (persistenceFile.exists()) {
191192
persistenceFile.delete();
192193
}
@@ -456,7 +457,7 @@ private void loadPersistedInteractions() {
456457
*/
457458
private int loadInteractionsForBin(RequestBin bin, java.io.File storageDir) {
458459
try {
459-
java.io.File dataFile = new java.io.File(storageDir, "interactions-" + bin.getCorrelationId() + ".json");
460+
java.io.File dataFile = StorageFileUtils.interactionsFile(storageDir, bin.getCorrelationId());
460461

461462
if (!dataFile.exists()) {
462463
api.logging().logToOutput("[BinService] No persisted interactions for bin: " + bin.getName());
@@ -530,7 +531,7 @@ public List<InteractshEntry> getPersistedInteractions(RequestBin bin) {
530531

531532
String userHome = System.getProperty("user.home");
532533
java.io.File storageDir = new java.io.File(userHome, ".requestbin-collaborator");
533-
java.io.File dataFile = new java.io.File(storageDir, "interactions-" + bin.getCorrelationId() + ".json");
534+
java.io.File dataFile = StorageFileUtils.interactionsFile(storageDir, bin.getCorrelationId());
534535

535536
if (!dataFile.exists()) {
536537
return interactions;
@@ -627,4 +628,4 @@ public void clearAllBins() {
627628
}
628629
api.logging().logToOutput("Cleared all bins");
629630
}
630-
}
631+
}

src/burp/services/PollingService.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import burp.api.montoya.MontoyaApi;
44
import burp.models.BinServer;
55
import burp.models.Correlation;
6+
import burp.util.StorageFileUtils;
67
import burp.utils.CryptoUtils;
78
import interactsh.InteractshEntry;
89
import org.json.JSONArray;
@@ -333,8 +334,8 @@ private void saveInteractionToStorage(InteractshEntry entry, Correlation correla
333334
api.logging().logToOutput("[PollingService] Created storage directory: " + storageDir.getAbsolutePath() + " (success: " + created + ")");
334335
}
335336

336-
// File for this bin unique ID
337-
File dataFile = new File(storageDir, "interactions-" + binUniqueId + ".json");
337+
// File for this bin unique ID (sanitized to prevent path traversal)
338+
File dataFile = StorageFileUtils.interactionsFile(storageDir, binUniqueId);
338339

339340
JSONArray storedInteractions;
340341
if (dataFile.exists()) {
@@ -529,4 +530,4 @@ public String getBinUniqueId() {
529530
return binUniqueId;
530531
}
531532
}
532-
}
533+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package burp.util;
2+
3+
import java.io.File;
4+
import java.util.Locale;
5+
6+
/**
7+
* Utilities for safely constructing local storage file paths.
8+
*/
9+
public final class StorageFileUtils {
10+
private static final int MAX_IDENTIFIER_LENGTH = 128;
11+
private static final String DEFAULT_IDENTIFIER = "unknown";
12+
13+
private StorageFileUtils() {
14+
// Utility class
15+
}
16+
17+
/**
18+
* Sanitizes an identifier so it is safe to use as part of a filename.
19+
*/
20+
public static String sanitizeIdentifierForFilename(String identifier) {
21+
if (identifier == null) {
22+
return DEFAULT_IDENTIFIER;
23+
}
24+
25+
String sanitized = identifier
26+
.trim()
27+
.replace('\\', '_')
28+
.replace('/', '_')
29+
.replaceAll("[^a-zA-Z0-9._-]", "_");
30+
31+
// Avoid hidden-file style names and parent-directory patterns.
32+
while (sanitized.startsWith(".")) {
33+
sanitized = sanitized.substring(1);
34+
}
35+
sanitized = sanitized.replace("..", "_");
36+
37+
if (sanitized.isEmpty()) {
38+
sanitized = DEFAULT_IDENTIFIER;
39+
}
40+
41+
if (sanitized.length() > MAX_IDENTIFIER_LENGTH) {
42+
sanitized = sanitized.substring(0, MAX_IDENTIFIER_LENGTH);
43+
}
44+
45+
return sanitized.toLowerCase(Locale.ROOT);
46+
}
47+
48+
/**
49+
* Builds the storage file path for a bin/interaction identifier.
50+
*/
51+
public static File interactionsFile(File storageDir, String identifier) {
52+
String safeIdentifier = sanitizeIdentifierForFilename(identifier);
53+
return new File(storageDir, "interactions-" + safeIdentifier + ".json");
54+
}
55+
}

0 commit comments

Comments
 (0)