mirror of
https://github.com/aljazceru/cowrie.git
synced 2025-12-18 22:44:29 +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 os
|
||||||
import time
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from zope.interface import implementer
|
from zope.interface import implementer
|
||||||
|
|
||||||
import twisted
|
import twisted
|
||||||
from twisted.conch import avatar, interfaces as conchinterfaces
|
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 session
|
||||||
from twisted.conch.ssh import transport
|
|
||||||
from twisted.conch.ssh import filetransfer
|
from twisted.conch.ssh import filetransfer
|
||||||
from twisted.conch.ssh import forwarding
|
from twisted.conch.ssh import forwarding
|
||||||
from twisted.conch.ssh.filetransfer import FXF_READ, FXF_WRITE, FXF_APPEND, FXF_CREAT, FXF_TRUNC, FXF_EXCL
|
from twisted.conch.ssh.filetransfer import FXF_READ, FXF_WRITE, FXF_APPEND, FXF_CREAT, FXF_TRUNC, FXF_EXCL
|
||||||
import twisted.conch.ls
|
import twisted.conch.ls
|
||||||
from twisted.python import log, components
|
from twisted.python import log, components
|
||||||
from twisted.conch.openssh_compat import primes
|
from twisted.conch.ssh.common import getNS
|
||||||
from twisted.conch.ssh.common import NS, getNS
|
|
||||||
from twisted.internet import defer
|
|
||||||
from twisted.protocols.policies import TimeoutMixin
|
|
||||||
|
|
||||||
from cowrie.core import credentials
|
|
||||||
from cowrie.core import auth
|
|
||||||
from cowrie.core import pwd
|
from cowrie.core import pwd
|
||||||
from cowrie.core import connection
|
|
||||||
from cowrie.core import honeypot
|
|
||||||
from cowrie.core import protocol
|
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)
|
@implementer(conchinterfaces.ISFTPFile)
|
||||||
class CowrieSFTPFile:
|
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.core.config import readConfigFile
|
||||||
from cowrie import core
|
from cowrie import core
|
||||||
import cowrie.core.ssh
|
import cowrie.core.transport
|
||||||
import cowrie.core.realm
|
import cowrie.core.realm
|
||||||
import cowrie.core.checkers
|
import cowrie.core.checkers
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ class CowrieServiceMaker(object):
|
|||||||
else:
|
else:
|
||||||
listen_port = 2222
|
listen_port = 2222
|
||||||
|
|
||||||
factory = core.ssh.HoneyPotSSHFactory(cfg)
|
factory = core.transport.HoneyPotSSHFactory(cfg)
|
||||||
factory.portal = portal.Portal(core.realm.HoneyPotRealm(cfg))
|
factory.portal = portal.Portal(core.realm.HoneyPotRealm(cfg))
|
||||||
factory.portal.registerChecker(
|
factory.portal.registerChecker(
|
||||||
core.checkers.HoneypotPublicKeyChecker())
|
core.checkers.HoneypotPublicKeyChecker())
|
||||||
|
|||||||
Reference in New Issue
Block a user