mirror of
https://github.com/aljazceru/cowrie.git
synced 2025-12-17 22:14:19 +01:00
split ssh.py in multiple files to manage easier
This commit is contained in:
64
cowrie/core/keys.py
Normal file
64
cowrie/core/keys.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>
|
||||
# See the COPYRIGHT file for more information
|
||||
|
||||
"""
|
||||
This module contains ...
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from twisted.conch.ssh import keys
|
||||
from twisted.python import log
|
||||
|
||||
|
||||
def getRSAKeys(cfg):
|
||||
"""
|
||||
"""
|
||||
publicKeyFile = cfg.get('honeypot', 'rsa_public_key')
|
||||
privateKeyFile = cfg.get('honeypot', 'rsa_private_key')
|
||||
if not (os.path.exists(publicKeyFile) and os.path.exists(privateKeyFile)):
|
||||
log.msg("Generating new RSA keypair...")
|
||||
from Crypto.PublicKey import RSA
|
||||
from twisted.python import randbytes
|
||||
KEY_LENGTH = 2048
|
||||
rsaKey = RSA.generate(KEY_LENGTH, randbytes.secureRandom)
|
||||
publicKeyString = keys.Key(rsaKey).public().toString('openssh')
|
||||
privateKeyString = keys.Key(rsaKey).toString('openssh')
|
||||
with open(publicKeyFile, 'w+b') as f:
|
||||
f.write(publicKeyString)
|
||||
with open(privateKeyFile, 'w+b') as f:
|
||||
f.write(privateKeyString)
|
||||
else:
|
||||
with open(publicKeyFile, 'r') as f:
|
||||
publicKeyString = f.read()
|
||||
with open(privateKeyFile, 'r') as f:
|
||||
privateKeyString = f.read()
|
||||
return publicKeyString, privateKeyString
|
||||
|
||||
|
||||
|
||||
def getDSAKeys(cfg):
|
||||
"""
|
||||
"""
|
||||
publicKeyFile = cfg.get('honeypot', 'dsa_public_key')
|
||||
privateKeyFile = cfg.get('honeypot', 'dsa_private_key')
|
||||
if not (os.path.exists(publicKeyFile) and os.path.exists(privateKeyFile)):
|
||||
log.msg("Generating new DSA keypair...")
|
||||
from Crypto.PublicKey import DSA
|
||||
from twisted.python import randbytes
|
||||
KEY_LENGTH = 1024
|
||||
dsaKey = DSA.generate(KEY_LENGTH, randbytes.secureRandom)
|
||||
publicKeyString = keys.Key(dsaKey).public().toString('openssh')
|
||||
privateKeyString = keys.Key(dsaKey).toString('openssh')
|
||||
with open(publicKeyFile, 'w+b') as f:
|
||||
f.write(publicKeyString)
|
||||
with open(privateKeyFile, 'w+b') as f:
|
||||
f.write(privateKeyString)
|
||||
else:
|
||||
with open(publicKeyFile, 'r') as f:
|
||||
publicKeyString = f.read()
|
||||
with open(privateKeyFile, 'r') as f:
|
||||
privateKeyString = f.read()
|
||||
return publicKeyString, privateKeyString
|
||||
|
||||
|
||||
@@ -6,298 +6,21 @@ This module contains ...
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from zope.interface import implementer
|
||||
|
||||
import twisted
|
||||
from twisted.conch import avatar, interfaces as conchinterfaces
|
||||
from twisted.conch.ssh import factory
|
||||
from twisted.conch.ssh import keys
|
||||
from twisted.conch.ssh import session
|
||||
from twisted.conch.ssh import transport
|
||||
from twisted.conch.ssh import filetransfer
|
||||
from twisted.conch.ssh import forwarding
|
||||
from twisted.conch.ssh.filetransfer import FXF_READ, FXF_WRITE, FXF_APPEND, FXF_CREAT, FXF_TRUNC, FXF_EXCL
|
||||
import twisted.conch.ls
|
||||
from twisted.python import log, components
|
||||
from twisted.conch.openssh_compat import primes
|
||||
from twisted.conch.ssh.common import NS, getNS
|
||||
from twisted.internet import defer
|
||||
from twisted.protocols.policies import TimeoutMixin
|
||||
from twisted.conch.ssh.common import getNS
|
||||
|
||||
from cowrie.core import credentials
|
||||
from cowrie.core import auth
|
||||
from cowrie.core import pwd
|
||||
from cowrie.core import connection
|
||||
from cowrie.core import honeypot
|
||||
from cowrie.core import protocol
|
||||
from cowrie.core import server
|
||||
from cowrie.core import userauth
|
||||
|
||||
|
||||
class HoneyPotSSHFactory(factory.SSHFactory):
|
||||
"""
|
||||
"""
|
||||
|
||||
services = {
|
||||
'ssh-userauth': userauth.HoneyPotSSHUserAuthServer,
|
||||
'ssh-connection': connection.CowrieSSHConnection,
|
||||
}
|
||||
|
||||
def __init__(self, cfg):
|
||||
self.cfg = cfg
|
||||
|
||||
|
||||
def logDispatch(self, *msg, **args):
|
||||
"""
|
||||
Special delivery to the loggers to avoid scope problems
|
||||
"""
|
||||
for dblog in self.dbloggers:
|
||||
dblog.logDispatch(*msg, **args)
|
||||
for output in self.output_plugins:
|
||||
output.logDispatch(*msg, **args)
|
||||
|
||||
|
||||
def startFactory(self):
|
||||
"""
|
||||
"""
|
||||
|
||||
# Interactive protocols are kept here for the interact feature
|
||||
self.sessions = {}
|
||||
|
||||
# For use by the uptime command
|
||||
self.starttime = time.time()
|
||||
|
||||
# Load/create keys
|
||||
rsaPubKeyString, rsaPrivKeyString = getRSAKeys(self.cfg)
|
||||
dsaPubKeyString, dsaPrivKeyString = getDSAKeys(self.cfg)
|
||||
self.publicKeys = {'ssh-rsa': keys.Key.fromString(data=rsaPubKeyString),
|
||||
'ssh-dss': keys.Key.fromString(data=dsaPubKeyString)}
|
||||
self.privateKeys = {'ssh-rsa': keys.Key.fromString(data=rsaPrivKeyString),
|
||||
'ssh-dss': keys.Key.fromString(data=dsaPrivKeyString)}
|
||||
|
||||
# Load db loggers
|
||||
self.dbloggers = []
|
||||
for x in self.cfg.sections():
|
||||
if not x.startswith('database_'):
|
||||
continue
|
||||
engine = x.split('_')[1]
|
||||
try:
|
||||
dblogger = __import__( 'cowrie.dblog.{}'.format(engine),
|
||||
globals(), locals(), ['dblog']).DBLogger(self.cfg)
|
||||
log.addObserver(dblogger.emit)
|
||||
self.dbloggers.append(dblogger)
|
||||
log.msg("Loaded dblog engine: {}".format(engine))
|
||||
except:
|
||||
log.err()
|
||||
log.msg("Failed to load dblog engine: {}".format(engine))
|
||||
|
||||
# Load output modules
|
||||
self.output_plugins = []
|
||||
for x in self.cfg.sections():
|
||||
if not x.startswith('output_'):
|
||||
continue
|
||||
engine = x.split('_')[1]
|
||||
try:
|
||||
output = __import__( 'cowrie.output.{}'.format(engine),
|
||||
globals(), locals(), ['output']).Output(self.cfg)
|
||||
log.addObserver(output.emit)
|
||||
self.output_plugins.append(output)
|
||||
log.msg("Loaded output engine: {}".format(engine))
|
||||
except:
|
||||
log.err()
|
||||
log.msg("Failed to load output engine: {}".format(engine))
|
||||
|
||||
|
||||
factory.SSHFactory.startFactory(self)
|
||||
|
||||
|
||||
def stopFactory(self):
|
||||
"""
|
||||
"""
|
||||
factory.SSHFactory.stopFactory(self)
|
||||
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
"""
|
||||
Create an instance of the server side of the SSH protocol.
|
||||
|
||||
@type addr: L{twisted.internet.interfaces.IAddress} provider
|
||||
@param addr: The address at which the server will listen.
|
||||
|
||||
@rtype: L{cowrie.core.HoneyPotTransport}
|
||||
@return: The built transport.
|
||||
"""
|
||||
|
||||
_modulis = '/etc/ssh/moduli', '/private/etc/moduli'
|
||||
|
||||
t = HoneyPotTransport()
|
||||
|
||||
try:
|
||||
t.ourVersionString = self.cfg.get('honeypot', 'ssh_version_string')
|
||||
except:
|
||||
t.ourVersionString = "SSH-2.0-OpenSSH_6.0p1 Debian-4+deb7u2"
|
||||
|
||||
t.supportedPublicKeys = list(self.privateKeys.keys())
|
||||
|
||||
for _moduli in _modulis:
|
||||
try:
|
||||
self.primes = primes.parseModuliFile(_moduli)
|
||||
break
|
||||
except IOError as err:
|
||||
pass
|
||||
|
||||
if not self.primes:
|
||||
log.msg("Moduli not found, disabling diffie-hellman-group-exchange-sha1")
|
||||
ske = t.supportedKeyExchanges[:]
|
||||
ske.remove('diffie-hellman-group-exchange-sha1')
|
||||
t.supportedKeyExchanges = ske
|
||||
|
||||
# Reorder supported ciphers to resemble current openssh more
|
||||
t.supportedCiphers = ['aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-cbc', '3des-cbc', 'blowfish-cbc', 'cast128-cbc', 'aes192-cbc', 'aes256-cbc']
|
||||
t.supportedPublicKeys = ['ssh-rsa', 'ssh-dss']
|
||||
t.supportedMACs = ['hmac-md5', 'hmac-sha1']
|
||||
|
||||
t.factory = self
|
||||
return t
|
||||
|
||||
|
||||
|
||||
class HoneyPotTransport(transport.SSHServerTransport, TimeoutMixin):
|
||||
"""
|
||||
"""
|
||||
|
||||
def connectionMade(self):
|
||||
"""
|
||||
Called when the connection is made from the other side.
|
||||
We send our version, but wait with sending KEXINIT
|
||||
"""
|
||||
self.transportId = uuid.uuid4().hex[:8]
|
||||
|
||||
log.msg(eventid='KIPP0001',
|
||||
format='New connection: %(src_ip)s:%(src_port)s (%(dst_ip)s:%(dst_port)s) [session: %(sessionno)s]',
|
||||
src_ip=self.transport.getPeer().host, src_port=self.transport.getPeer().port,
|
||||
dst_ip=self.transport.getHost().host, dst_port=self.transport.getHost().port,
|
||||
id=self.transportId, sessionno=self.transport.sessionno)
|
||||
|
||||
self.transport.write('{}\r\n'.format(self.ourVersionString))
|
||||
self.currentEncryptions = transport.SSHCiphers('none', 'none', 'none', 'none')
|
||||
self.currentEncryptions.setKeys('', '', '', '', '', '')
|
||||
self.setTimeout(120)
|
||||
|
||||
|
||||
def sendKexInit(self):
|
||||
"""
|
||||
Don't send key exchange prematurely
|
||||
"""
|
||||
if not self.gotVersion:
|
||||
return
|
||||
transport.SSHServerTransport.sendKexInit(self)
|
||||
|
||||
|
||||
def dataReceived(self, data):
|
||||
"""
|
||||
First, check for the version string (SSH-2.0-*). After that has been
|
||||
received, this method adds data to the buffer, and pulls out any
|
||||
packets.
|
||||
|
||||
@type data: C{str}
|
||||
"""
|
||||
self.buf = self.buf + data
|
||||
if not self.gotVersion:
|
||||
if not '\n' in self.buf:
|
||||
return
|
||||
self.otherVersionString = self.buf.split('\n')[0].strip()
|
||||
if self.buf.startswith('SSH-'):
|
||||
self.gotVersion = True
|
||||
remoteVersion = self.buf.split('-')[1]
|
||||
if remoteVersion not in self.supportedVersions:
|
||||
self._unsupportedVersionReceived(remoteVersion)
|
||||
return
|
||||
i = self.buf.index('\n')
|
||||
self.buf = self.buf[i+1:]
|
||||
self.sendKexInit()
|
||||
else:
|
||||
self.transport.write('Protocol mismatch.\n')
|
||||
log.msg('Bad protocol version identification: %s' % (self.otherVersionString,))
|
||||
self.transport.loseConnection()
|
||||
return
|
||||
packet = self.getPacket()
|
||||
while packet:
|
||||
messageNum = ord(packet[0])
|
||||
self.dispatchMessage(messageNum, packet[1:])
|
||||
packet = self.getPacket()
|
||||
|
||||
# Later versions seem to call sendKexInit again on their own
|
||||
if twisted.version.major < 11 and \
|
||||
not self._hadVersion and self.gotVersion:
|
||||
self.sendKexInit()
|
||||
self._hadVersion = True
|
||||
|
||||
|
||||
def ssh_KEXINIT(self, packet):
|
||||
"""
|
||||
"""
|
||||
k = getNS(packet[16:], 10)
|
||||
strings, rest = k[:-1], k[-1]
|
||||
(kexAlgs, keyAlgs, encCS, encSC, macCS, macSC, compCS, compSC, langCS,
|
||||
langSC) = [s.split(',') for s in strings]
|
||||
log.msg(eventid='KIPP0009', version=self.otherVersionString,
|
||||
kexAlgs=kexAlgs, keyAlgs=keyAlgs, encCS=encCS, macCS=macCS,
|
||||
compCS=compCS, format='Remote SSH version: %(version)s')
|
||||
|
||||
return transport.SSHServerTransport.ssh_KEXINIT(self, packet)
|
||||
|
||||
|
||||
def timeoutConnection(self):
|
||||
"""
|
||||
"""
|
||||
log.msg( "Authentication Timeout reached" )
|
||||
self.transport.loseConnection()
|
||||
|
||||
|
||||
def setService(self, service):
|
||||
"""
|
||||
Remove login grace timeout
|
||||
"""
|
||||
if service.name == "ssh-connection":
|
||||
self.setTimeout(None)
|
||||
transport.SSHServerTransport.setService(self, service)
|
||||
|
||||
|
||||
def connectionLost(self, reason):
|
||||
"""
|
||||
This seems to be the only reliable place of catching lost connection
|
||||
"""
|
||||
self.setTimeout(None)
|
||||
if self.transport.sessionno in self.factory.sessions:
|
||||
del self.factory.sessions[self.transport.sessionno]
|
||||
transport.SSHServerTransport.connectionLost(self, reason)
|
||||
self.transport.connectionLost(reason)
|
||||
self.transport = None
|
||||
log.msg(eventid='KIPP0011', format='Connection lost')
|
||||
|
||||
|
||||
def sendDisconnect(self, reason, desc):
|
||||
"""
|
||||
http://kbyte.snowpenguin.org/portal/2013/04/30/kippo-protocol-mismatch-workaround/
|
||||
Workaround for the "bad packet length" error message.
|
||||
|
||||
@param reason: the reason for the disconnect. Should be one of the
|
||||
DISCONNECT_* values.
|
||||
@type reason: C{int}
|
||||
@param desc: a descrption of the reason for the disconnection.
|
||||
@type desc: C{str}
|
||||
"""
|
||||
if not 'bad packet length' in desc:
|
||||
transport.SSHServerTransport.sendDisconnect(self, reason, desc)
|
||||
else:
|
||||
self.transport.write('Packet corrupt\n')
|
||||
log.msg('[SERVER] - Disconnecting with error, code %s\nreason: %s'
|
||||
% (reason, desc))
|
||||
self.transport.loseConnection()
|
||||
|
||||
|
||||
|
||||
@@ -480,58 +203,6 @@ class SSHSessionForCowrieUser:
|
||||
|
||||
|
||||
|
||||
def getRSAKeys(cfg):
|
||||
"""
|
||||
"""
|
||||
publicKeyFile = cfg.get('honeypot', 'rsa_public_key')
|
||||
privateKeyFile = cfg.get('honeypot', 'rsa_private_key')
|
||||
if not (os.path.exists(publicKeyFile) and os.path.exists(privateKeyFile)):
|
||||
log.msg("Generating new RSA keypair...")
|
||||
from Crypto.PublicKey import RSA
|
||||
from twisted.python import randbytes
|
||||
KEY_LENGTH = 2048
|
||||
rsaKey = RSA.generate(KEY_LENGTH, randbytes.secureRandom)
|
||||
publicKeyString = keys.Key(rsaKey).public().toString('openssh')
|
||||
privateKeyString = keys.Key(rsaKey).toString('openssh')
|
||||
with open(publicKeyFile, 'w+b') as f:
|
||||
f.write(publicKeyString)
|
||||
with open(privateKeyFile, 'w+b') as f:
|
||||
f.write(privateKeyString)
|
||||
else:
|
||||
with open(publicKeyFile, 'r') as f:
|
||||
publicKeyString = f.read()
|
||||
with open(privateKeyFile, 'r') as f:
|
||||
privateKeyString = f.read()
|
||||
return publicKeyString, privateKeyString
|
||||
|
||||
|
||||
|
||||
def getDSAKeys(cfg):
|
||||
"""
|
||||
"""
|
||||
publicKeyFile = cfg.get('honeypot', 'dsa_public_key')
|
||||
privateKeyFile = cfg.get('honeypot', 'dsa_private_key')
|
||||
if not (os.path.exists(publicKeyFile) and os.path.exists(privateKeyFile)):
|
||||
log.msg("Generating new DSA keypair...")
|
||||
from Crypto.PublicKey import DSA
|
||||
from twisted.python import randbytes
|
||||
KEY_LENGTH = 1024
|
||||
dsaKey = DSA.generate(KEY_LENGTH, randbytes.secureRandom)
|
||||
publicKeyString = keys.Key(dsaKey).public().toString('openssh')
|
||||
privateKeyString = keys.Key(dsaKey).toString('openssh')
|
||||
with open(publicKeyFile, 'w+b') as f:
|
||||
f.write(publicKeyString)
|
||||
with open(privateKeyFile, 'w+b') as f:
|
||||
f.write(privateKeyString)
|
||||
else:
|
||||
with open(publicKeyFile, 'r') as f:
|
||||
publicKeyString = f.read()
|
||||
with open(privateKeyFile, 'r') as f:
|
||||
privateKeyString = f.read()
|
||||
return publicKeyString, privateKeyString
|
||||
|
||||
|
||||
|
||||
@implementer(conchinterfaces.ISFTPFile)
|
||||
class CowrieSFTPFile:
|
||||
"""
|
||||
|
||||
291
cowrie/core/transport.py
Normal file
291
cowrie/core/transport.py
Normal file
@@ -0,0 +1,291 @@
|
||||
# Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>
|
||||
# See the COPYRIGHT file for more information
|
||||
|
||||
"""
|
||||
This module contains ...
|
||||
"""
|
||||
|
||||
import time
|
||||
import uuid
|
||||
|
||||
import twisted
|
||||
from twisted.conch.ssh import factory
|
||||
from twisted.conch.ssh import keys
|
||||
from twisted.conch.ssh import transport
|
||||
from twisted.python import log
|
||||
from twisted.conch.openssh_compat import primes
|
||||
from twisted.conch.ssh.common import getNS
|
||||
from twisted.protocols.policies import TimeoutMixin
|
||||
|
||||
from cowrie.core import connection
|
||||
from cowrie.core import userauth
|
||||
from cowrie.core import keys as cowriekeys
|
||||
|
||||
|
||||
class HoneyPotSSHFactory(factory.SSHFactory):
|
||||
"""
|
||||
This factory creates HoneyPotTransport instances
|
||||
"""
|
||||
|
||||
services = {
|
||||
'ssh-userauth': userauth.HoneyPotSSHUserAuthServer,
|
||||
'ssh-connection': connection.CowrieSSHConnection,
|
||||
}
|
||||
|
||||
def __init__(self, cfg):
|
||||
self.cfg = cfg
|
||||
|
||||
|
||||
def logDispatch(self, *msg, **args):
|
||||
"""
|
||||
Special delivery to the loggers to avoid scope problems
|
||||
"""
|
||||
for dblog in self.dbloggers:
|
||||
dblog.logDispatch(*msg, **args)
|
||||
for output in self.output_plugins:
|
||||
output.logDispatch(*msg, **args)
|
||||
|
||||
|
||||
def startFactory(self):
|
||||
"""
|
||||
"""
|
||||
|
||||
# Interactive protocols are kept here for the interact feature
|
||||
self.sessions = {}
|
||||
|
||||
# For use by the uptime command
|
||||
self.starttime = time.time()
|
||||
|
||||
# Load/create keys
|
||||
rsaPubKeyString, rsaPrivKeyString = cowriekeys.getRSAKeys(self.cfg)
|
||||
dsaPubKeyString, dsaPrivKeyString = cowriekeys.getDSAKeys(self.cfg)
|
||||
self.publicKeys = {
|
||||
'ssh-rsa': keys.Key.fromString(data=rsaPubKeyString),
|
||||
'ssh-dss': keys.Key.fromString(data=dsaPubKeyString)}
|
||||
self.privateKeys = {
|
||||
'ssh-rsa': keys.Key.fromString(data=rsaPrivKeyString),
|
||||
'ssh-dss': keys.Key.fromString(data=dsaPrivKeyString)}
|
||||
|
||||
# Load db loggers
|
||||
self.dbloggers = []
|
||||
for x in self.cfg.sections():
|
||||
if not x.startswith('database_'):
|
||||
continue
|
||||
engine = x.split('_')[1]
|
||||
try:
|
||||
dblogger = __import__( 'cowrie.dblog.{}'.format(engine),
|
||||
globals(), locals(), ['dblog']).DBLogger(self.cfg)
|
||||
log.addObserver(dblogger.emit)
|
||||
self.dbloggers.append(dblogger)
|
||||
log.msg("Loaded dblog engine: {}".format(engine))
|
||||
except:
|
||||
log.err()
|
||||
log.msg("Failed to load dblog engine: {}".format(engine))
|
||||
|
||||
# Load output modules
|
||||
self.output_plugins = []
|
||||
for x in self.cfg.sections():
|
||||
if not x.startswith('output_'):
|
||||
continue
|
||||
engine = x.split('_')[1]
|
||||
try:
|
||||
output = __import__( 'cowrie.output.{}'.format(engine),
|
||||
globals(), locals(), ['output']).Output(self.cfg)
|
||||
log.addObserver(output.emit)
|
||||
self.output_plugins.append(output)
|
||||
log.msg("Loaded output engine: {}".format(engine))
|
||||
except:
|
||||
log.err()
|
||||
log.msg("Failed to load output engine: {}".format(engine))
|
||||
|
||||
factory.SSHFactory.startFactory(self)
|
||||
|
||||
|
||||
def stopFactory(self):
|
||||
"""
|
||||
"""
|
||||
factory.SSHFactory.stopFactory(self)
|
||||
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
"""
|
||||
Create an instance of the server side of the SSH protocol.
|
||||
|
||||
@type addr: L{twisted.internet.interfaces.IAddress} provider
|
||||
@param addr: The address at which the server will listen.
|
||||
|
||||
@rtype: L{cowrie.core.HoneyPotTransport}
|
||||
@return: The built transport.
|
||||
"""
|
||||
|
||||
_modulis = '/etc/ssh/moduli', '/private/etc/moduli'
|
||||
|
||||
t = HoneyPotTransport()
|
||||
|
||||
try:
|
||||
t.ourVersionString = self.cfg.get('honeypot', 'ssh_version_string')
|
||||
except:
|
||||
t.ourVersionString = "SSH-2.0-OpenSSH_6.0p1 Debian-4+deb7u2"
|
||||
|
||||
t.supportedPublicKeys = list(self.privateKeys.keys())
|
||||
|
||||
for _moduli in _modulis:
|
||||
try:
|
||||
self.primes = primes.parseModuliFile(_moduli)
|
||||
break
|
||||
except IOError as err:
|
||||
pass
|
||||
|
||||
if not self.primes:
|
||||
ske = t.supportedKeyExchanges[:]
|
||||
ske.remove('diffie-hellman-group-exchange-sha1')
|
||||
t.supportedKeyExchanges = ske
|
||||
log.msg("No moduli, disabled diffie-hellman-group-exchange-sha1")
|
||||
|
||||
# Reorder supported ciphers to resemble current openssh more
|
||||
t.supportedCiphers = ['aes128-ctr', 'aes192-ctr', 'aes256-ctr',
|
||||
'aes128-cbc', '3des-cbc', 'blowfish-cbc', 'cast128-cbc',
|
||||
'aes192-cbc', 'aes256-cbc']
|
||||
t.supportedPublicKeys = ['ssh-rsa', 'ssh-dss']
|
||||
t.supportedMACs = ['hmac-md5', 'hmac-sha1']
|
||||
|
||||
t.factory = self
|
||||
return t
|
||||
|
||||
|
||||
|
||||
class HoneyPotTransport(transport.SSHServerTransport, TimeoutMixin):
|
||||
"""
|
||||
"""
|
||||
|
||||
def connectionMade(self):
|
||||
"""
|
||||
Called when the connection is made from the other side.
|
||||
We send our version, but wait with sending KEXINIT
|
||||
"""
|
||||
self.transportId = uuid.uuid4().hex[:8]
|
||||
|
||||
log.msg(eventid='KIPP0001',
|
||||
format='New connection: %(src_ip)s:%(src_port)s (%(dst_ip)s:%(dst_port)s) [session: %(sessionno)s]',
|
||||
src_ip=self.transport.getPeer().host, src_port=self.transport.getPeer().port,
|
||||
dst_ip=self.transport.getHost().host, dst_port=self.transport.getHost().port,
|
||||
id=self.transportId, sessionno=self.transport.sessionno)
|
||||
|
||||
self.transport.write('{}\r\n'.format(self.ourVersionString))
|
||||
self.currentEncryptions = transport.SSHCiphers('none', 'none', 'none', 'none')
|
||||
self.currentEncryptions.setKeys('', '', '', '', '', '')
|
||||
self.setTimeout(120)
|
||||
|
||||
|
||||
def sendKexInit(self):
|
||||
"""
|
||||
Don't send key exchange prematurely
|
||||
"""
|
||||
if not self.gotVersion:
|
||||
return
|
||||
transport.SSHServerTransport.sendKexInit(self)
|
||||
|
||||
|
||||
def dataReceived(self, data):
|
||||
"""
|
||||
First, check for the version string (SSH-2.0-*). After that has been
|
||||
received, this method adds data to the buffer, and pulls out any
|
||||
packets.
|
||||
|
||||
@type data: C{str}
|
||||
"""
|
||||
self.buf = self.buf + data
|
||||
if not self.gotVersion:
|
||||
if not '\n' in self.buf:
|
||||
return
|
||||
self.otherVersionString = self.buf.split('\n')[0].strip()
|
||||
if self.buf.startswith('SSH-'):
|
||||
self.gotVersion = True
|
||||
remoteVersion = self.buf.split('-')[1]
|
||||
if remoteVersion not in self.supportedVersions:
|
||||
self._unsupportedVersionReceived(remoteVersion)
|
||||
return
|
||||
i = self.buf.index('\n')
|
||||
self.buf = self.buf[i+1:]
|
||||
self.sendKexInit()
|
||||
else:
|
||||
self.transport.write('Protocol mismatch.\n')
|
||||
log.msg('Bad protocol version identification: %s' % (self.otherVersionString,))
|
||||
self.transport.loseConnection()
|
||||
return
|
||||
packet = self.getPacket()
|
||||
while packet:
|
||||
messageNum = ord(packet[0])
|
||||
self.dispatchMessage(messageNum, packet[1:])
|
||||
packet = self.getPacket()
|
||||
|
||||
# Later versions seem to call sendKexInit again on their own
|
||||
if twisted.version.major < 11 and \
|
||||
not self._hadVersion and self.gotVersion:
|
||||
self.sendKexInit()
|
||||
self._hadVersion = True
|
||||
|
||||
|
||||
def ssh_KEXINIT(self, packet):
|
||||
"""
|
||||
"""
|
||||
k = getNS(packet[16:], 10)
|
||||
strings, rest = k[:-1], k[-1]
|
||||
(kexAlgs, keyAlgs, encCS, encSC, macCS, macSC, compCS, compSC, langCS,
|
||||
langSC) = [s.split(',') for s in strings]
|
||||
log.msg(eventid='KIPP0009', version=self.otherVersionString,
|
||||
kexAlgs=kexAlgs, keyAlgs=keyAlgs, encCS=encCS, macCS=macCS,
|
||||
compCS=compCS, format='Remote SSH version: %(version)s')
|
||||
|
||||
return transport.SSHServerTransport.ssh_KEXINIT(self, packet)
|
||||
|
||||
|
||||
def timeoutConnection(self):
|
||||
"""
|
||||
"""
|
||||
log.msg( "Authentication Timeout reached" )
|
||||
self.transport.loseConnection()
|
||||
|
||||
|
||||
def setService(self, service):
|
||||
"""
|
||||
Remove login grace timeout
|
||||
"""
|
||||
if service.name == "ssh-connection":
|
||||
self.setTimeout(None)
|
||||
transport.SSHServerTransport.setService(self, service)
|
||||
|
||||
|
||||
def connectionLost(self, reason):
|
||||
"""
|
||||
This seems to be the only reliable place of catching lost connection
|
||||
"""
|
||||
self.setTimeout(None)
|
||||
if self.transport.sessionno in self.factory.sessions:
|
||||
del self.factory.sessions[self.transport.sessionno]
|
||||
transport.SSHServerTransport.connectionLost(self, reason)
|
||||
self.transport.connectionLost(reason)
|
||||
self.transport = None
|
||||
log.msg(eventid='KIPP0011', format='Connection lost')
|
||||
|
||||
|
||||
def sendDisconnect(self, reason, desc):
|
||||
"""
|
||||
http://kbyte.snowpenguin.org/portal/2013/04/30/kippo-protocol-mismatch-workaround/
|
||||
Workaround for the "bad packet length" error message.
|
||||
|
||||
@param reason: the reason for the disconnect. Should be one of the
|
||||
DISCONNECT_* values.
|
||||
@type reason: C{int}
|
||||
@param desc: a descrption of the reason for the disconnection.
|
||||
@type desc: C{str}
|
||||
"""
|
||||
if not 'bad packet length' in desc:
|
||||
transport.SSHServerTransport.sendDisconnect(self, reason, desc)
|
||||
else:
|
||||
self.transport.write('Packet corrupt\n')
|
||||
log.msg('[SERVER] - Disconnecting with error, code %s\nreason: %s'
|
||||
% (reason, desc))
|
||||
self.transport.loseConnection()
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ from twisted.cred import portal
|
||||
|
||||
from cowrie.core.config import readConfigFile
|
||||
from cowrie import core
|
||||
import cowrie.core.ssh
|
||||
import cowrie.core.transport
|
||||
import cowrie.core.realm
|
||||
import cowrie.core.checkers
|
||||
|
||||
@@ -93,7 +93,7 @@ class CowrieServiceMaker(object):
|
||||
else:
|
||||
listen_port = 2222
|
||||
|
||||
factory = core.ssh.HoneyPotSSHFactory(cfg)
|
||||
factory = core.transport.HoneyPotSSHFactory(cfg)
|
||||
factory.portal = portal.Portal(core.realm.HoneyPotRealm(cfg))
|
||||
factory.portal.registerChecker(
|
||||
core.checkers.HoneypotPublicKeyChecker())
|
||||
|
||||
Reference in New Issue
Block a user