@@ -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