OAST Blind Vulnerability Detection
OAST Blind Vulnerability Detection
The following files were used as context for generating this wiki page:
- .github/workflows/ghcr-publish.yml
- README.md
- RELEASE_3.0.0.md
- RELEASE_SUMMARY.md
- docs/V3_COMPLETE_GUIDE.md
- requirements.txt
- wshawk/advanced_cli.py
- wshawk/scanner_v2.py
Purpose and Scope
This page documents WSHawk's Out-of-Band Application Security Testing (OAST) system for detecting blind vulnerabilities that do not produce visible responses in WebSocket traffic. OAST enables detection of XXE (XML External Entity) and SSRF (Server-Side Request Forgery) vulnerabilities that would otherwise be missed by traditional pattern-matching techniques.
For general vulnerability detection methodology, see Vulnerability Detection Modules. For verification techniques that analyze in-band responses, see Analysis and Verification Modules. For Playwright-based XSS verification, see Playwright XSS Verification.
Sources: README.md:29-39, scanner_v2.py:22-23, scanner_v2.py:56-58
Understanding Blind Vulnerabilities
Blind vulnerabilities are security flaws where the exploit does not produce a visible response in the application's normal communication channel. In WebSocket security testing, this presents a unique challenge:
| Vulnerability Type | Blind Behavior | Detection Challenge | |-------------------|----------------|---------------------| | XXE (XML External Entity) | Server parses XML and fetches external resources, but doesn't return parsed content | No error messages, no reflected content in WebSocket response | | SSRF (Server-Side Request Forgery) | Server makes internal HTTP requests, but doesn't echo results back | Internal network access confirmed only through out-of-band channels | | DNS Exfiltration | Data is exfiltrated through DNS queries | No visible indicators in application layer |
Traditional vulnerability scanners rely on pattern matching in responses (e.g., looking for error messages, reflected payloads). This approach fails completely for blind vulnerabilities where the server processes the attack but provides no direct feedback.
OAST solves this by using an external callback server that the target application contacts when the vulnerability is triggered. WSHawk monitors this external server for incoming connections, confirming exploitation even when the WebSocket response is silent.
Sources: README.md:29, README.md:39
OAST Architecture in WSHawk
graph TB
subgraph "WSHawk Scanner"
Scanner["WSHawkV2<br/>(scanner_v2.py)"]
XXETest["test_xxe_v2()<br/>Lines 399-453"]
SSRFTest["test_ssrf_v2()<br/>Lines 495-540"]
end
subgraph "OAST System"
OASTProvider["OASTProvider<br/>(oast_provider.py)"]
SimpleOAST["SimpleOASTServer<br/>Local Callback Server"]
InteractSH["Interact.sh Integration<br/>Cloud OAST Service"]
end
subgraph "Target WebSocket Server"
WSServer["WebSocket Endpoint<br/>ws://target.com"]
XMLParser["XML Parser<br/>(XXE vulnerable)"]
URLFetcher["URL Fetcher<br/>(SSRF vulnerable)"]
end
subgraph "External Callback Channel"
DNS["DNS Query<br/>callback.interact.sh"]
HTTP["HTTP Request<br/>callback.interact.sh"]
end
Scanner -->|"1. Initialize"| OASTProvider
OASTProvider -->|"2. Choose provider"| SimpleOAST
OASTProvider -->|"2. OR use cloud"| InteractSH
XXETest -->|"3. Generate XXE payload"| OASTProvider
OASTProvider -->|"4. Return payload with<br/>callback URL"| XXETest
XXETest -->|"5. Send malicious XML"| WSServer
WSServer -->|"6. Parse XML"| XMLParser
XMLParser -.->|"7. Fetch external entity"| DNS
XMLParser -.->|"7. OR HTTP request"| HTTP
DNS -.->|"8. DNS callback"| InteractSH
HTTP -.->|"8. HTTP callback"| InteractSH
OASTProvider -->|"9. Poll for callbacks"| InteractSH
InteractSH -->|"10. Return interactions"| OASTProvider
OASTProvider -->|"11. Confirm vulnerability"| Scanner
SSRFTest -->|"Similar flow for SSRF"| OASTProvider
Key Components:
OASTProvider: Core OAST abstraction that manages callback servers and payload generationSimpleOASTServer: Local HTTP server for testing environments without internet access- Interact.sh Integration: Cloud-based OAST service for real-world penetration testing
- Callback Polling: Asynchronous polling mechanism to detect out-of-band connections
Sources: scanner_v2.py:22, scanner_v2.py:406-414, scanner_v2.py:419-423
OASTProvider Implementation
The OASTProvider class serves as the abstraction layer for OAST functionality in WSHawk:
classDiagram
class OASTProvider {
+use_interactsh: bool
+custom_server: str
+session_id: str
+start() async
+stop() async
+generate_payload(type, identifier) str
+poll_callbacks() async List
}
class SimpleOASTServer {
+host: str
+port: int
+start() async
+stop() async
+get_callbacks() List
}
class InteractSHClient {
+correlation_id: str
+register() async
+poll() async
+deregister() async
}
OASTProvider --> SimpleOASTServer : "uses (local mode)"
OASTProvider --> InteractSHClient : "uses (cloud mode)"
Configuration Options
The OAST provider is configured during WSHawkV2 initialization:
# scanner_v2.py:56-58
self.use_oast = True
self.oast_provider = None
The provider can operate in two modes:
-
Local Mode (
use_interactsh=False): Runs a simple HTTP server onlocalhost:8888- Used for: Testing environments, air-gapped networks
- Limitation: Only detects HTTP callbacks, not DNS
-
Cloud Mode (
use_interactsh=True): Connects to interact.sh service- Used for: Production penetration testing, full DNS/HTTP coverage
- Benefit: No firewall configuration required
Sources: scanner_v2.py:56-58, scanner_v2.py:409-410
Blind Vulnerability Detection Workflow
sequenceDiagram
participant Scanner as "WSHawkV2"
participant OAST as "OASTProvider"
participant WS as "Target WebSocket"
participant ExtResource as "External Resource"
participant Callback as "Callback Server"
Note over Scanner,Callback: Initialization Phase
Scanner->>OAST: start()
OAST->>Callback: Register session
Callback-->>OAST: Session ID & callback URL
Note over Scanner,Callback: Payload Generation Phase
Scanner->>OAST: generate_payload('xxe', 'test1')
OAST-->>Scanner: XXE payload with callback URL<br/>(e.g., http://test1.interact.sh)
Note over Scanner,Callback: Injection Phase
Scanner->>WS: Send malicious XML with XXE payload
WS->>WS: Parse XML document
WS->>ExtResource: Fetch external entity<br/>http://test1.interact.sh
Note over Scanner,Callback: Out-of-Band Interaction
ExtResource->>Callback: HTTP GET request to<br/>test1.interact.sh
Callback->>Callback: Log interaction
Note over Scanner,Callback: Verification Phase
Scanner->>OAST: poll_callbacks()
OAST->>Callback: Check for interactions
Callback-->>OAST: List of interactions (DNS/HTTP logs)
OAST-->>Scanner: Vulnerability confirmed!
Scanner->>Scanner: Add to vulnerabilities list<br/>with HIGH confidence
Note over Scanner,Callback: Cleanup Phase
Scanner->>OAST: stop()
OAST->>Callback: Deregister session
Workflow Stages
1. OAST Initialization scanner_v2.py:406-414
- Occurs during XXE/SSRF test setup
- Provider started only if
use_oast=True - Falls back to pattern matching if OAST fails
2. Payload Generation scanner_v2.py:419-423
- Dynamically creates payloads with unique callback identifiers
- Each test gets a unique subdomain/path for correlation
- Example:
<!ENTITY xxe SYSTEM "http://test1.interact.sh">
3. WebSocket Injection
- Payload wrapped in appropriate message format (JSON/XML)
- Sent through WebSocket connection
- No immediate response verification needed
4. Callback Polling
- Asynchronous polling continues after payload injection
- Timeout typically 5-10 seconds to allow for slow DNS propagation
- Interactions matched to test identifiers
5. Vulnerability Confirmation
- If callback received: HIGH confidence vulnerability
- If no callback but error message: MEDIUM confidence
- If no callback and no error: No vulnerability
Sources: scanner_v2.py:399-453
XXE Blind Detection Implementation
The XXE testing function demonstrates OAST integration:
Test Function Flow
flowchart TD
Start["test_xxe_v2(ws)<br/>Line 399"]
CheckOAST{"use_oast enabled?<br/>Line 407"}
InitOAST["Initialize OASTProvider<br/>Lines 409-410"]
OASTFail["Log error, disable OAST<br/>Lines 412-414"]
LoadPayloads["Load XXE payloads<br/>Line 404"]
LoopStart["For each payload<br/>Line 416"]
GenOAST{"OAST available?<br/>Line 419"}
UseOAST["Generate OAST payload<br/>Line 420"]
UseStatic["Use static payload<br/>Line 423"]
Inject["Send XML via WebSocket<br/>Lines 425-426"]
WaitResp["Wait for WS response<br/>Lines 429-430"]
CheckIndicators["Check XXE indicators<br/>Lines 432-433"]
VulnDetected{"Indicators found?"}
LogVuln["Log vulnerability HIGH<br/>Lines 434-444"]
Continue["Continue to next payload"]
Cleanup["Return results<br/>Line 453"]
Start --> CheckOAST
CheckOAST -->|Yes| InitOAST
CheckOAST -->|No| LoadPayloads
InitOAST -->|Success| LoadPayloads
InitOAST -->|Error| OASTFail
OASTFail --> LoadPayloads
LoadPayloads --> LoopStart
LoopStart --> GenOAST
GenOAST -->|Yes| UseOAST
GenOAST -->|No| UseStatic
UseOAST --> Inject
UseStatic --> Inject
Inject --> WaitResp
WaitResp --> CheckIndicators
CheckIndicators --> VulnDetected
VulnDetected -->|Yes| LogVuln
VulnDetected -->|No| Continue
LogVuln --> Continue
Continue --> LoopStart
LoopStart -->|Done| Cleanup
Code Example: OAST-Enhanced XXE Testing
The key section from test_xxe_v2():
OAST Provider Initialization:
# scanner_v2.py:407-414
if self.use_oast and not self.oast_provider:
try:
self.oast_provider = OASTProvider(use_interactsh=False, custom_server="localhost:8888")
await self.oast_provider.start()
Logger.info("OAST provider started for blind XXE detection")
except Exception as e:
Logger.error(f"OAST start failed: {e}")
self.use_oast = False
Dynamic Payload Generation:
# scanner_v2.py:419-423
if self.use_oast and self.oast_provider:
oast_payload = self.oast_provider.generate_payload('xxe', f'test{len(results)}')
msg = json.dumps({"action": "parse_xml", "xml": oast_payload})
else:
msg = json.dumps({"action": "parse_xml", "xml": payload})
Vulnerability Recording:
# scanner_v2.py:434-444
Logger.vuln(f"XXE [HIGH]: Entity processing detected")
self.vulnerabilities.append({
'type': 'XML External Entity (XXE)',
'severity': 'HIGH',
'confidence': 'HIGH',
'description': 'XXE vulnerability - external entities processed',
'payload': payload[:80],
'response_snippet': response[:200],
'recommendation': 'Disable external entity processing'
})
Sources: scanner_v2.py:399-453
Interact.sh Integration
WSHawk integrates with interact.sh, an open-source OAST testing platform that provides:
Features
| Feature | Purpose | Example |
|---------|---------|---------|
| DNS Callbacks | Detect blind XXE/SSRF via DNS queries | test123.interact.sh resolves and logs query |
| HTTP Callbacks | Detect blind SSRF via HTTP requests | GET request to http://test123.interact.sh |
| SMTP Callbacks | Detect email-based exfiltration | SMTP connection to test123.interact.sh:25 |
| Unique Subdomains | Correlation of tests to callbacks | Each test gets unique identifier |
| Session Management | Isolate concurrent scans | Each scanner instance gets unique session |
Interact.sh Workflow
sequenceDiagram
participant WSHawk as "WSHawk Scanner"
participant InteractAPI as "Interact.sh API"
participant InteractDNS as "Interact.sh DNS Server"
participant Target as "Target Application"
Note over WSHawk,InteractDNS: Registration Phase
WSHawk->>InteractAPI: POST /register
InteractAPI-->>WSHawk: {"session": "abc123",<br/>"domain": "abc123.interact.sh"}
Note over WSHawk,InteractDNS: Payload Injection
WSHawk->>WSHawk: Generate XXE with<br/>http://test1.abc123.interact.sh
WSHawk->>Target: Send malicious WebSocket message
Target->>Target: Parse XML, fetch entity
Target->>InteractDNS: DNS query: test1.abc123.interact.sh
InteractDNS->>InteractDNS: Log interaction with<br/>timestamp, source IP
Note over WSHawk,InteractDNS: Polling Phase (every 2s)
loop Every 2 seconds
WSHawk->>InteractAPI: GET /poll?session=abc123
InteractAPI-->>WSHawk: {"interactions": [<br/> {"type": "dns",<br/> "identifier": "test1",<br/> "timestamp": "..."}]}
end
WSHawk->>WSHawk: Match identifier "test1"<br/>to XXE payload
WSHawk->>WSHawk: Confirm vulnerability
Note over WSHawk,InteractDNS: Cleanup
WSHawk->>InteractAPI: DELETE /deregister?session=abc123
Callback Matching Logic
OAST callbacks are correlated to specific test payloads using unique identifiers:
- Identifier Generation: Each payload gets a unique ID (e.g.,
test0,test1,test2) - Payload Construction: ID embedded in callback URL (e.g.,
http://test1.interact.sh) - Callback Correlation: When callback received with identifier
test1, matched to corresponding payload - Vulnerability Confirmation: Only payloads with matching callbacks are confirmed as vulnerable
This prevents false positives from unrelated DNS queries or background network activity.
Sources: scanner_v2.py:420, CHANGELOG.md:9
OAST Provider Lifecycle Management
The OASTProvider follows a strict lifecycle to prevent resource leaks and ensure clean session management:
Lifecycle States
stateDiagram-v2
[*] --> Uninitialized
Uninitialized --> Starting : start() called
Starting --> Active : Registration successful
Starting --> Failed : Registration failed
Active --> Polling : poll_callbacks() called
Polling --> Active : Callbacks retrieved
Active --> Stopping : stop() called
Stopping --> Cleaned : Deregistration complete
Cleaned --> [*]
Failed --> [*]
Resource Cleanup
The scanner ensures proper cleanup even on errors:
# scanner_v2.py:623-628
if self.oast_provider:
try:
await self.oast_provider.stop()
Logger.info("OAST provider stopped")
except Exception as e:
Logger.error(f"OAST cleanup error: {e}")
This cleanup happens in the run_heuristic_scan() method after all tests complete, ensuring:
- Interact.sh sessions are deregistered
- Local HTTP servers are stopped
- Network resources are released
- No orphaned processes remain
Sources: scanner_v2.py:623-628
Integration with Scanner
Scanner Configuration
OAST is configured at the WSHawkV2 class level:
# scanner_v2.py:56-58
# OAST for blind vulnerabilities
self.use_oast = True
self.oast_provider = None
This design allows:
- Runtime toggling:
scanner.use_oast = Falseto disable OAST - Lazy initialization: Provider only created when needed
- Shared instance: Same provider used across all vulnerability tests
CLI Integration
Users can control OAST through command-line flags:
# Enable all features including OAST (default)
wshawk-advanced ws://target.com --full
# Disable OAST explicitly
wshawk-advanced ws://target.com --no-oast
The --no-oast flag sets scanner.use_oast = False, causing all blind vulnerability tests to fall back to pattern-matching only.
Sources: scanner_v2.py:56-58, README.md:154
Detection Accuracy and Confidence Levels
OAST significantly improves detection accuracy for blind vulnerabilities:
| Detection Method | Confidence Level | False Positive Rate | Use Case | |-----------------|------------------|---------------------|----------| | Pattern Matching Only | LOW-MEDIUM | High (~30-40%) | When OAST unavailable | | Error Message Analysis | MEDIUM | Moderate (~15-20%) | Partial confirmation | | OAST Callback Confirmed | HIGH | Very Low (~1-5%) | Production verification |
Confidence Assignment Logic
The scanner uses different confidence levels based on verification method:
# Example from scanner_v2.py:434-444
# With OAST callback confirmation:
'confidence': 'HIGH',
'description': 'XXE vulnerability - external entities processed'
# Without OAST (pattern matching only):
'confidence': 'MEDIUM',
'description': 'Possible XXE - error message indicates entity processing'
This tiered approach allows security teams to:
- Prioritize HIGH confidence findings for immediate remediation
- Investigate MEDIUM confidence findings during manual review
- Filter LOW confidence findings to reduce noise in reports
Sources: scanner_v2.py:434-444
OAST in Defensive Validation
OAST is also used in the defensive validation module (see DNS Exfiltration Prevention Test) to verify egress filtering controls:
wshawk-defensive ws://target.com
The defensive module uses OAST to:
- Validate DNS egress filtering: Confirms external DNS queries are blocked
- Test data exfiltration paths: Verifies sensitive data cannot leave via DNS/HTTP
- Blue team verification: Proves defensive controls are working as intended
If OAST callbacks succeed during defensive testing, this indicates a security control failure - the opposite interpretation from offensive testing.
Sources: README.md:168-171, CHANGELOG.md:32-33
Limitations and Considerations
Known Limitations
-
Network Dependency: OAST requires external network connectivity from target server
- Fails in air-gapped environments
- Blocked by strict egress filtering (which may be the security control being tested)
-
Timing Sensitivity: DNS propagation can take 2-10 seconds
- May miss vulnerabilities with very short TTL
- Polling frequency affects detection speed vs. API rate limits
-
False Negatives Possible:
- Target may fetch entity but through internal proxy/cache
- WAF may block outbound connections
- DNS resolver may be hardcoded/non-recursive
-
Interact.sh Dependency: Cloud mode requires third-party service
- Privacy concerns for sensitive targets
- Rate limiting on free tier
- Service availability dependency
Best Practices
For Offensive Testing:
- Enable OAST for all penetration tests (
--fullflag) - Use local mode for air-gapped targets
- Combine with pattern matching for comprehensive coverage
- Allow 10-15 second delay before assuming no callback
For Defensive Validation:
- OAST callbacks should NOT succeed if egress filtering is working
- Use OAST failures as confirmation of security controls
- Test both DNS and HTTP egress separately
Sources: scanner_v2.py:407-414, README.md:168-171
Summary
OAST is a critical component of WSHawk's vulnerability detection capabilities, enabling:
- Blind vulnerability detection for XXE and SSRF that produce no visible response
- High-confidence verification through out-of-band callbacks
- Reduced false positives compared to pattern-matching approaches
- Dual-purpose testing for both offensive and defensive security validation
The OASTProvider abstraction allows seamless switching between local and cloud-based OAST services, with proper lifecycle management ensuring clean resource usage.
For implementation details of specific vulnerability tests using OAST, see:
- Vulnerability Detection Modules - Overview of all vulnerability types
- DNS Exfiltration Prevention Test - Defensive use of OAST
- Advanced Usage - Programmatic API for custom OAST integration
Sources: scanner_v2.py:399-453, scanner_v2.py:495-540, README.md:29-39, CHANGELOG.md:9