Skip to content

Commit da7b503

Browse files
author
atomiczsec
committed
fix aborting too early
1 parent 2216ed5 commit da7b503

1 file changed

Lines changed: 78 additions & 22 deletions

File tree

src/main/java/burp/RequestSender.java

Lines changed: 78 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,13 @@ private static Pattern[] compilePatterns(String... rawPatterns) {
139139
private static final Pattern UUID_PATTERN = Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", Pattern.CASE_INSENSITIVE);
140140

141141
/**
142-
* Initial test to check if the application ignores trailing path segments.
143-
* This is a prerequisite for cache deception - the backend must ignore trailing segments.
142+
* Initial test to check if the application ignores trailing path segments or supports normalization.
143+
* This is a prerequisite for cache deception - the backend must ignore trailing segments or support normalization.
144144
* Returns true only if:
145145
* 1. Authenticated and unauthenticated responses are DIFFERENT (confirms auth is required)
146-
* 2. Authenticated response with appended segment is SIMILAR to original (confirms backend ignores trailing segments)
146+
* 2. Either:
147+
* a. Appended path returns 200 with SIMILAR content (confirms backend ignores trailing segments), OR
148+
* b. Normalization pattern returns 200 with SIMILAR content (confirms normalization-based cache deception possible)
147149
*/
148150
protected static InitialTestResult initialTest(IHttpRequestResponse message) {
149151
byte[] orgRequest = buildHttpRequest(message, null, null, true);
@@ -178,32 +180,86 @@ protected static InitialTestResult initialTest(IHttpRequestResponse message) {
178180
return InitialTestResult.failure("Endpoint looks public (auth vs unauth are similar)");
179181
}
180182

181-
// Step 2: Verify that appending a random segment returns SIMILAR content
182-
// This confirms the backend ignores trailing path segments (prerequisite for cache deception)
183+
// Step 2a: Try simple appending first (e.g., /my-account/abc)
184+
// 404 is expected and indicates origin server doesn't abstract the path - this is OK
183185
String randomSegment = generateRandomString(5);
184186
byte[] testRequest = buildHttpRequestWithSegment(message, randomSegment, null, true, "/");
185187
Map<String, Object> appendedDetails = retrieveResponseDetails(message.getHttpService(), testRequest);
186-
if (appendedDetails == null) {
187-
return InitialTestResult.failure("Unable to fetch appended path response");
188-
}
189-
int appendedStatusCode = (int) appendedDetails.get("statusCode");
190-
if (appendedStatusCode < 200 || appendedStatusCode >= 300) {
191-
return InitialTestResult.failure("Appended path returned status " + appendedStatusCode);
188+
if (appendedDetails != null) {
189+
int appendedStatusCode = (int) appendedDetails.get("statusCode");
190+
if (appendedStatusCode >= 200 && appendedStatusCode < 300) {
191+
// Simple appending worked - check similarity
192+
byte[] appendedBody = (byte[]) appendedDetails.get("body");
193+
Map<String, Object> step2Similarity = testSimilar(
194+
bytesToString(originalAuthBody, orgDetails),
195+
bytesToString(appendedBody, appendedDetails));
196+
boolean appendIsSimilar = (boolean) step2Similarity.get("similar");
197+
198+
if (appendIsSimilar) {
199+
// Simple appending works - backend ignores trailing segments
200+
return InitialTestResult.success(randomSegment);
201+
}
202+
} else if (appendedStatusCode == 404) {
203+
// 404 is expected - indicates origin server doesn't abstract the path
204+
// This is fine, we'll try normalization patterns next
205+
BurpExtender.logDebug("Appended path returned 404 (expected) - trying normalization patterns");
206+
}
192207
}
193-
byte[] appendedBody = (byte[]) appendedDetails.get("body");
194-
195-
Map<String, Object> step2Similarity = testSimilar(
196-
bytesToString(originalAuthBody, orgDetails),
197-
bytesToString(appendedBody, appendedDetails));
198-
boolean appendIsSimilar = (boolean) step2Similarity.get("similar");
199208

200-
if (!appendIsSimilar) {
201-
BurpExtender.logDebug("Initial test failed: Appended segment response not similar to original");
202-
return InitialTestResult.failure("Backend rejects extra path segments");
209+
// Step 2b: Try normalization patterns (e.g., /aaa/..%2fmy-account)
210+
// This tests if normalization discrepancies can be exploited
211+
IRequestInfo reqInfo = BurpExtender.getHelpers().analyzeRequest(message);
212+
String targetPath = reqInfo.getUrl().getPath();
213+
if (targetPath == null || targetPath.isEmpty() || !targetPath.startsWith("/")) {
214+
return InitialTestResult.failure("Invalid target path");
215+
}
216+
217+
// Extract the path without leading slash for normalization pattern
218+
String pathWithoutSlash = targetPath.substring(1);
219+
220+
// Test normalization patterns with different traversal patterns
221+
// Pattern: /{arbitraryDir}/{traversal}{originalPath}
222+
// Example: /aaa/..%2fmy-account normalizes to /my-account
223+
String[] traversalPatterns = {"..%2f", "%2f%2e%2e%2f", "%2e%2e%2f"};
224+
String[] arbitraryDirs = {"aaa", "test", "xyz", "dir"};
225+
226+
for (String traversal : traversalPatterns) {
227+
for (String dir : arbitraryDirs) {
228+
// Build pattern: /{arbitraryDir}/{traversal}{originalPath}
229+
// Example: /aaa/..%2fmy-account
230+
String normalizationPath = "/" + dir + "/" + traversal + pathWithoutSlash;
231+
232+
byte[] normRequest = buildHttpRequestWithFullPath(message, true, normalizationPath);
233+
if (normRequest == null) {
234+
continue;
235+
}
236+
237+
Map<String, Object> normDetails = retrieveResponseDetails(message.getHttpService(), normRequest);
238+
if (normDetails == null) {
239+
continue;
240+
}
241+
242+
int normStatusCode = (int) normDetails.get("statusCode");
243+
if (normStatusCode >= 200 && normStatusCode < 300) {
244+
// Normalization pattern returned 200 - check similarity
245+
byte[] normBody = (byte[]) normDetails.get("body");
246+
Map<String, Object> normSimilarity = testSimilar(
247+
bytesToString(originalAuthBody, orgDetails),
248+
bytesToString(normBody, normDetails));
249+
boolean normIsSimilar = (boolean) normSimilarity.get("similar");
250+
251+
if (normIsSimilar) {
252+
// Normalization pattern works - cache deception via normalization is possible
253+
BurpExtender.logDebug("Normalization pattern successful: " + normalizationPath);
254+
return InitialTestResult.success(randomSegment);
255+
}
256+
}
257+
}
203258
}
204259

205-
// Both conditions met: auth required AND backend ignores trailing segments
206-
return InitialTestResult.success(randomSegment);
260+
// Neither simple appending nor normalization patterns worked
261+
BurpExtender.logDebug("Initial test failed: Neither appended segments nor normalization patterns returned similar content");
262+
return InitialTestResult.failure("Backend rejects extra path segments and normalization patterns");
207263
}
208264

209265
/**

0 commit comments

Comments
 (0)