Add a Telnet proxy for Cowrie (#1159)

* add telnet proxy
This commit is contained in:
Guilherme Borges
2019-07-01 18:41:03 +01:00
committed by Michel Oosterhof
parent cc7d65adc0
commit 34f8464732
11 changed files with 730 additions and 173 deletions

View File

@ -199,9 +199,43 @@ auth_class = UserDB
backend_ssh_host = localhost
backend_ssh_port = 2022
backend_telnet_host = localhost
backend_telnet_port = 2023
backend_user = root
backend_pass = root
# Telnet Spoofing #
# To spoof telnet authentication we need to capture login and password prompts, and spoof data to the backend in order
# to successfully authenticate. If disabled, attackers can only use the real user credentials of the backend.
telnet_spoof_authentication = true
# These regex were made using Ubuntu 18.04; you have to adapt these for the prompts
# from your backend. You can enable raw logging above to analyse data passing through
# and identify the format of the prompts you need.
# You should generally include ".*" at the beginning and end of prompts, since Telnet messages can contain
# more data than the prompt.
# For login it is usually <hostname> login:
telnet_username_prompt_regex = (\n|^)46438fdf5ebc login: .*
# Password prompt is usually only the word Password
telnet_password_prompt_regex = .*Password: .*
# This data is sent by clients at the beginning of negotiation (before the password prompt), and contains the username
# that is trying to log in. We replace that username with the one in "backend_user" to allow the chance of a successful
# login after the first password prompt. We are only able to check if credentials are allowed after the password is
# inserted. If they are, then a correct username was already sent and authentication succeeds; if not, we send a fake
# password to force authentication to fail.
telnet_username_in_negotiation_regex = (.*\xff\xfa.*USER\x01)(.*?)(\xff.*)
# Other Configs #
# log raw TCP packets in SSh and Telnet
log_raw = false
# ============================================================================
# Shell Options
# Options around Cowrie's Shell Emulation
@ -474,8 +508,6 @@ forward_tunnel = false
# Configure keyboard-interactive login
auth_keyboard_interactive_enabled = false
# ============================================================================
# Telnet Specific Options
# ============================================================================

View File

@ -117,7 +117,7 @@ class FrontendSSHTransport(transport.SSHServerTransport, TimeoutMixin):
d.addErrback(self.pool_connection_error)
else:
# simply a proxy, no pool
backend_ip = CowrieConfig().get('proxy', 'backend_ip')
backend_ip = CowrieConfig().get('proxy', 'backend_ssh_host')
backend_port = CowrieConfig().getint('proxy', 'backend_ssh_port')
self.connect_to_backend(backend_ip, backend_port)

View File

@ -0,0 +1,62 @@
# Copyright (C) 2015, 2016 GoSecure Inc.
"""
Telnet Transport and Authentication for the Honeypot
@author: Olivier Bilodeau <obilodeau@gosecure.ca>
"""
from __future__ import absolute_import, division
import time
from twisted.internet import protocol
from twisted.python import log
from cowrie.core.config import CowrieConfig
from cowrie.telnet.transport import CowrieTelnetTransport
from cowrie.telnet.userauth import HoneyPotTelnetAuthProtocol
from cowrie.telnet_proxy.server_transport import FrontendTelnetTransport
class HoneyPotTelnetFactory(protocol.ServerFactory):
"""
This factory creates HoneyPotTelnetAuthProtocol instances
They listen directly to the TCP port
"""
tac = None
# TODO logging clarity can be improved: see what SSH does
def logDispatch(self, *msg, **args):
"""
Special delivery to the loggers to avoid scope problems
"""
args['sessionno'] = 'T{0}'.format(str(args['sessionno']))
for output in self.tac.output_plugins:
output.logDispatch(*msg, **args)
def startFactory(self):
try:
honeyfs = CowrieConfig().get('honeypot', 'contents_path')
issuefile = honeyfs + "/etc/issue.net"
self.banner = open(issuefile, 'rb').read()
except IOError:
self.banner = b""
# For use by the uptime command
self.starttime = time.time()
# hook protocol
if CowrieConfig().get('honeypot', 'backend', fallback='shell') == 'proxy':
self.protocol = lambda: FrontendTelnetTransport()
else:
self.protocol = lambda: CowrieTelnetTransport(HoneyPotTelnetAuthProtocol, self.portal)
protocol.ServerFactory.startFactory(self)
log.msg("Ready to accept Telnet connections")
def stopFactory(self):
"""
Stop output plugins
"""
protocol.ServerFactory.stopFactory(self)

View File

@ -7,181 +7,14 @@ Telnet Transport and Authentication for the Honeypot
from __future__ import absolute_import, division
import struct
import time
import uuid
from twisted.conch.telnet import AlreadyNegotiating, AuthenticatingTelnetProtocol, ITelnetProtocol, TelnetTransport
from twisted.conch.telnet import ECHO, LINEMODE, NAWS, SGA
from twisted.internet import protocol
from twisted.conch.telnet import AlreadyNegotiating, TelnetTransport
from twisted.protocols.policies import TimeoutMixin
from twisted.python import log
from cowrie.core.config import CowrieConfig
from cowrie.core.credentials import UsernamePasswordIP
class HoneyPotTelnetFactory(protocol.ServerFactory):
"""
This factory creates HoneyPotTelnetAuthProtocol instances
They listen directly to the TCP port
"""
tac = None
# TODO logging clarity can be improved: see what SSH does
def logDispatch(self, *msg, **args):
"""
Special delivery to the loggers to avoid scope problems
"""
args['sessionno'] = 'T{0}'.format(str(args['sessionno']))
for output in self.tac.output_plugins:
output.logDispatch(*msg, **args)
def startFactory(self):
try:
honeyfs = CowrieConfig().get('honeypot', 'contents_path')
issuefile = honeyfs + "/etc/issue.net"
self.banner = open(issuefile, 'rb').read()
except IOError:
self.banner = b""
# For use by the uptime command
self.starttime = time.time()
# hook protocol
self.protocol = lambda: CowrieTelnetTransport(HoneyPotTelnetAuthProtocol, self.portal)
protocol.ServerFactory.startFactory(self)
log.msg("Ready to accept Telnet connections")
def stopFactory(self):
"""
Stop output plugins
"""
protocol.ServerFactory.stopFactory(self)
class HoneyPotTelnetAuthProtocol(AuthenticatingTelnetProtocol):
"""
TelnetAuthProtocol that takes care of Authentication. Once authenticated this
protocol is replaced with HoneyPotTelnetSession.
"""
loginPrompt = b'login: '
passwordPrompt = b'Password: '
windowSize = [40, 80]
def connectionMade(self):
# self.transport.negotiationMap[NAWS] = self.telnet_NAWS
# Initial option negotation. Want something at least for Mirai
# for opt in (NAWS,):
# self.transport.doChain(opt).addErrback(log.err)
# I need to doubly escape here since my underlying
# CowrieTelnetTransport hack would remove it and leave just \n
self.transport.write(self.factory.banner.replace(b'\n', b'\r\r\n'))
self.transport.write(self.loginPrompt)
def connectionLost(self, reason):
"""
Fires on pre-authentication disconnects
"""
AuthenticatingTelnetProtocol.connectionLost(self, reason)
def telnet_User(self, line):
"""
Overridden to conditionally kill 'WILL ECHO' which confuses clients
that don't implement a proper Telnet protocol (most malware)
"""
self.username = line # .decode()
# only send ECHO option if we are chatting with a real Telnet client
self.transport.willChain(ECHO)
# FIXME: this should be configurable or provided via filesystem
self.transport.write(self.passwordPrompt)
return 'Password'
def telnet_Password(self, line):
username, password = self.username, line # .decode()
del self.username
def login(ignored):
self.src_ip = self.transport.getPeer().host
creds = UsernamePasswordIP(username, password, self.src_ip)
d = self.portal.login(creds, self.src_ip, ITelnetProtocol)
d.addCallback(self._cbLogin)
d.addErrback(self._ebLogin)
# are we dealing with a real Telnet client?
if self.transport.options:
# stop ECHO
# even if ECHO negotiation fails we still want to attempt a login
# this allows us to support dumb clients which is common in malware
# thus the addBoth: on success and on exception (AlreadyNegotiating)
self.transport.wontChain(ECHO).addBoth(login)
else:
# process login
login('')
return 'Discard'
def telnet_Command(self, command):
self.transport.protocol.dataReceived(command + b'\r')
return "Command"
def _cbLogin(self, ial):
"""
Fired on a successful login
"""
interface, protocol, logout = ial
protocol.windowSize = self.windowSize
self.protocol = protocol
self.logout = logout
self.state = 'Command'
self.transport.write(b'\n')
# Remove the short timeout of the login prompt.
self.transport.setTimeout(CowrieConfig().getint('honeypot', 'interactive_timeout', fallback=300))
# replace myself with avatar protocol
protocol.makeConnection(self.transport)
self.transport.protocol = protocol
def _ebLogin(self, failure):
# TODO: provide a way to have user configurable strings for wrong password
self.transport.wontChain(ECHO)
self.transport.write(b"\nLogin incorrect\n")
self.transport.write(self.loginPrompt)
self.state = "User"
def telnet_NAWS(self, data):
"""
From TelnetBootstrapProtocol in twisted/conch/telnet.py
"""
if len(data) == 4:
width, height = struct.unpack('!HH', b''.join(data))
self.windowSize = [height, width]
else:
log.msg("Wrong number of NAWS bytes")
def enableLocal(self, opt):
if opt == ECHO:
return True
# TODO: check if twisted now supports SGA (see git commit c58056b0)
elif opt == SGA:
return False
else:
return False
def enableRemote(self, opt):
# TODO: check if twisted now supports LINEMODE (see git commit c58056b0)
if opt == LINEMODE:
return False
elif opt == NAWS:
return True
elif opt == SGA:
return True
else:
return False
class CowrieTelnetTransport(TelnetTransport, TimeoutMixin):

View File

@ -0,0 +1,142 @@
# Copyright (C) 2015, 2016 GoSecure Inc.
"""
Telnet Transport and Authentication for the Honeypot
@author: Olivier Bilodeau <obilodeau@gosecure.ca>
"""
from __future__ import absolute_import, division
import struct
from twisted.conch.telnet import AuthenticatingTelnetProtocol, ITelnetProtocol
from twisted.conch.telnet import ECHO, LINEMODE, NAWS, SGA
from twisted.python import log
from cowrie.core.config import CowrieConfig
from cowrie.core.credentials import UsernamePasswordIP
class HoneyPotTelnetAuthProtocol(AuthenticatingTelnetProtocol):
"""
TelnetAuthProtocol that takes care of Authentication. Once authenticated this
protocol is replaced with HoneyPotTelnetSession.
"""
loginPrompt = b'login: '
passwordPrompt = b'Password: '
windowSize = [40, 80]
def connectionMade(self):
# self.transport.negotiationMap[NAWS] = self.telnet_NAWS
# Initial option negotation. Want something at least for Mirai
# for opt in (NAWS,):
# self.transport.doChain(opt).addErrback(log.err)
# I need to doubly escape here since my underlying
# CowrieTelnetTransport hack would remove it and leave just \n
self.transport.write(self.factory.banner.replace(b'\n', b'\r\r\n'))
self.transport.write(self.loginPrompt)
def connectionLost(self, reason):
"""
Fires on pre-authentication disconnects
"""
AuthenticatingTelnetProtocol.connectionLost(self, reason)
def telnet_User(self, line):
"""
Overridden to conditionally kill 'WILL ECHO' which confuses clients
that don't implement a proper Telnet protocol (most malware)
"""
self.username = line # .decode()
# only send ECHO option if we are chatting with a real Telnet client
self.transport.willChain(ECHO)
# FIXME: this should be configurable or provided via filesystem
self.transport.write(self.passwordPrompt)
return 'Password'
def telnet_Password(self, line):
username, password = self.username, line # .decode()
del self.username
def login(ignored):
self.src_ip = self.transport.getPeer().host
creds = UsernamePasswordIP(username, password, self.src_ip)
d = self.portal.login(creds, self.src_ip, ITelnetProtocol)
d.addCallback(self._cbLogin)
d.addErrback(self._ebLogin)
# are we dealing with a real Telnet client?
if self.transport.options:
# stop ECHO
# even if ECHO negotiation fails we still want to attempt a login
# this allows us to support dumb clients which is common in malware
# thus the addBoth: on success and on exception (AlreadyNegotiating)
self.transport.wontChain(ECHO).addBoth(login)
else:
# process login
login('')
return 'Discard'
def telnet_Command(self, command):
self.transport.protocol.dataReceived(command + b'\r')
return "Command"
def _cbLogin(self, ial):
"""
Fired on a successful login
"""
interface, protocol, logout = ial
protocol.windowSize = self.windowSize
self.protocol = protocol
self.logout = logout
self.state = 'Command'
self.transport.write(b'\n')
# Remove the short timeout of the login prompt.
self.transport.setTimeout(CowrieConfig().getint('honeypot', 'interactive_timeout', fallback=300))
# replace myself with avatar protocol
protocol.makeConnection(self.transport)
self.transport.protocol = protocol
def _ebLogin(self, failure):
# TODO: provide a way to have user configurable strings for wrong password
self.transport.wontChain(ECHO)
self.transport.write(b"\nLogin incorrect\n")
self.transport.write(self.loginPrompt)
self.state = "User"
def telnet_NAWS(self, data):
"""
From TelnetBootstrapProtocol in twisted/conch/telnet.py
"""
if len(data) == 4:
width, height = struct.unpack('!HH', b''.join(data))
self.windowSize = [height, width]
else:
log.msg("Wrong number of NAWS bytes")
def enableLocal(self, opt):
if opt == ECHO:
return True
# TODO: check if twisted now supports SGA (see git commit c58056b0)
elif opt == SGA:
return False
else:
return False
def enableRemote(self, opt):
# TODO: check if twisted now supports LINEMODE (see git commit c58056b0)
if opt == LINEMODE:
return False
elif opt == NAWS:
return True
elif opt == SGA:
return True
else:
return False

View File

@ -0,0 +1,14 @@
All username credentials, when sent to backend, have the configured username that is known to succeed (i.e. exist in
the backend). When we spoof the password we decide whether the login is valid or not, and in the second case we send
an invalid password, thus causing auth to fail.
# Caveats in the protocol:
* When username is being input (and chars are being sent to the backend), the **client expects to receive their echo**.
In our proxy, we do the echo locally, since **we don't want the backend to see our authentication** (in the end we send
to the backend what we want it to see). When we send the username in the end, the **backend then sends the full echo**
of the username, which look for and **ignore** in the proxy.
* Backspaces in authentication are sent as **0x7F from the frontend**, but it expects to **receive "0x08 0x08"
as echo**, so we also have to look for that in the proxy's handler.

View File

View File

@ -0,0 +1,68 @@
# Copyright (c) 2019 Guilherme Borges <guilhermerosasborges@gmail.com>
# All rights reserved.
from twisted.conch.telnet import TelnetTransport
from twisted.internet import protocol
from twisted.protocols.policies import TimeoutMixin
from twisted.python import log
class BackendTelnetTransport(TelnetTransport, TimeoutMixin):
def __init__(self):
# self.delayedPacketsToFrontend = []
self.backendConnected = False
self.telnetHandler = None
super(BackendTelnetTransport, self).__init__()
def connectionMade(self):
log.msg('Connected to Telnet backend at {0}'.format(self.transport.getPeer().host))
self.telnetHandler = self.factory.server.telnetHandler
self.telnetHandler.setClient(self)
self.backendConnected = True
self.factory.server.client = self
for packet in self.factory.server.delayedPacketsToBackend:
self.transport.write(packet)
self.factory.server.delayedPacketsToBackend = []
super().connectionMade()
# TODO timeout if no backend available
def connectionLost(self, reason):
# close transport on frontend
self.factory.server.loseConnection()
# signal that we're closing to the handler
self.telnetHandler.close()
def timeoutConnection(self):
"""
Make sure all sessions time out eventually.
Timeout is reset when authentication succeeds.
"""
log.msg('Timeout reached in BackendTelnetTransport')
# close transports on both sides
self.transport.loseConnection()
self.factory.server.transport.loseConnection()
# signal that we're closing to the handler
self.telnetHandler.close()
def dataReceived(self, data):
self.telnetHandler.addPacket('backend', data)
def write(self, data):
self.transport.write(data)
def packet_buffer(self, payload):
"""
We can only proceed if authentication has been performed between client and proxy.
Meanwhile we hold packets in here.
"""
self.factory.server.transport.write(payload)
class BackendTelnetFactory(protocol.ClientFactory):
protocol = BackendTelnetTransport

View File

@ -0,0 +1,283 @@
import os
import re
import time
from twisted.python import log
from cowrie.core import ttylog
from cowrie.core.checkers import HoneypotPasswordChecker
from cowrie.core.config import CowrieConfig
def process_backspaces(s):
"""
Takes a user-input string that might have backspaces in it (represented as 0x7F),
and actually performs the 'backspace operation' to return a clean string.
"""
n = b''
for i in range(len(s)):
char = chr(s[i]).encode()
if char == b'\x7f':
n = n[:-1]
else:
n += char
return n
def remove_all(original_string, remove_list):
"""
Removes all substrings in the list remove_list from string original_string.
"""
n = original_string
for substring in remove_list:
n = n.replace(substring, b'')
return n
class TelnetHandler:
def __init__(self, server):
# holds packet data; useful to manipulate it across functions as needed
self.currentData = None
self.sendData = True
# front and backend references
self.server = server
self.client = None
# definitions from config
self.spoofAuthenticationData = CowrieConfig().getboolean('proxy', 'telnet_spoof_authentication')
self.backendLogin = CowrieConfig().get('proxy', 'backend_user').encode()
self.backendPassword = CowrieConfig().get('proxy', 'backend_pass').encode()
self.usernameInNegotiationRegex = CowrieConfig().get('proxy', 'telnet_username_in_negotiation_regex',
raw=True).encode()
self.usernamePromptRegex = CowrieConfig().get('proxy', 'telnet_username_prompt_regex', raw=True).encode()
self.passwordPromptRegex = CowrieConfig().get('proxy', 'telnet_password_prompt_regex', raw=True).encode()
# telnet state
self.currentCommand = b''
# auth state
self.authStarted = False
self.authDone = False
self.usernameState = b'' # TODO clear on end
self.inputingLogin = False
self.passwordState = b'' # TODO clear on end
self.inputingPassword = False
self.waitingLoginEcho = False
# some data is sent by the backend right before the password prompt, we want to capture that
# and the respective frontend response and send it before starting to intercept auth data
self.prePasswordData = False
# tty logging
self.startTime = time.time()
self.ttylogPath = CowrieConfig().get('honeypot', 'ttylog_path')
self.ttylogEnabled = CowrieConfig().getboolean('honeypot', 'ttylog', fallback=True)
self.ttylogSize = 0
if self.ttylogEnabled:
self.ttylogFile = '{0}/telnet-{1}.log'.format(self.ttylogPath, time.strftime('%Y%m%d-%H%M%S'))
ttylog.ttylog_open(self.ttylogFile, self.startTime)
def setClient(self, client):
self.client = client
def close(self):
if self.ttylogEnabled:
ttylog.ttylog_close(self.ttylogFile, time.time())
shasum = ttylog.ttylog_inputhash(self.ttylogFile)
shasumfile = os.path.join(self.ttylogPath, shasum)
if os.path.exists(shasumfile):
duplicate = True
os.remove(self.ttylogFile)
else:
duplicate = False
os.rename(self.ttylogFile, shasumfile)
umask = os.umask(0)
os.umask(umask)
os.chmod(shasumfile, 0o666 & ~umask)
self.ttylogEnabled = False # do not close again if function called after closing
log.msg(eventid='cowrie.log.closed',
format='Closing TTY Log: %(ttylog)s after %(duration)d seconds',
ttylog=shasumfile,
size=self.ttylogSize,
shasum=shasum,
duplicate=duplicate,
duration=time.time() - self.startTime)
def sendBackend(self, data):
self.client.transport.write(data)
# log raw packets if user sets so
if CowrieConfig().getboolean('proxy', 'log_raw', fallback=False):
log.msg(b'to_backend - ' + data)
if self.ttylogEnabled and self.authStarted:
cleanData = data.replace(b'\x00', b'\n') # some frontends send 0xFF instead of newline
ttylog.ttylog_write(self.ttylogFile, len(data), ttylog.TYPE_INPUT, time.time(), cleanData)
self.ttylogSize += len(data)
def sendFrontend(self, data):
self.server.transport.write(data)
# log raw packets if user sets so
if CowrieConfig().getboolean('proxy', 'log_raw', fallback=False):
log.msg(b'to_frontend - ' + data)
if self.ttylogEnabled and self.authStarted:
ttylog.ttylog_write(self.ttylogFile, len(data), ttylog.TYPE_OUTPUT, time.time(), data)
# self.ttylogSize += len(data)
def addPacket(self, parent, data):
self.currentData = data
self.sendData = True
if self.spoofAuthenticationData and not self.authDone:
# detect prompts from backend
if parent == 'backend':
self.setProcessingStateBackend()
# detect patterns from frontend
if parent == 'frontend':
self.setProcessingStateFrontend()
# save user inputs from frontend
if parent == 'frontend':
if self.inputingPassword:
self.processPasswordInput()
if self.inputingLogin:
self.processUsernameInput()
# capture username echo from backend
if self.waitingLoginEcho and parent == 'backend':
self.currentData = self.currentData.replace(self.backendLogin + b'\r\n', b'')
self.waitingLoginEcho = False
# log user commands
if parent == 'frontend' and self.authDone:
self.currentCommand += data.replace(b'\r\x00', b'').replace(b'\r\n', b'')
# check if a command has terminated
if b'\r' in data:
if len(self.currentCommand) > 0:
log.msg('CMD: {0}'.format(self.currentCommand.decode()))
self.currentCommand = b''
# send data after processing (also check if processing did not reduce it to an empty string)
if self.sendData and len(self.currentData):
if parent == 'frontend':
self.sendBackend(self.currentData)
else:
self.sendFrontend(self.currentData)
def processUsernameInput(self):
self.sendData = False # withold data until input is complete
# remove control characters
control_chars = [b'\r', b'\x00', b'\n']
self.usernameState += remove_all(self.currentData, control_chars)
# backend echoes data back to user to show on terminal prompt
# - NULL char is replaced by NEWLINE by backend
# - 0x7F (backspace) is replaced by two 0x08 separated by a blankspace
self.sendFrontend(self.currentData.replace(b'\x7f', b'\x08 \x08').replace(b'\x00', b'\n'))
# check if done inputing
if b'\r' in self.currentData:
terminatingChar = chr(self.currentData[self.currentData.index(b'\r') + 1]).encode() # usually \n or \x00
# cleanup
self.usernameState = process_backspaces(self.usernameState)
log.msg('User input login: {0}'.format(self.usernameState))
self.inputingLogin = False
# actually send to backend
self.currentData = self.backendLogin + b'\r' + terminatingChar
self.sendData = True
# we now have to ignore the username echo from the backend in the next packet
self.waitingLoginEcho = True
def processPasswordInput(self):
self.sendData = False # withold data until input is complete
if self.prePasswordData:
self.sendBackend(self.currentData[:3])
self.prePasswordData = False
# remove control characters
control_chars = [b'\xff', b'\xfd', b'\x01', b'\r', b'\x00', b'\n']
self.passwordState += remove_all(self.currentData, control_chars)
# check if done inputing
if b'\r' in self.currentData:
terminatingChar = chr(self.currentData[self.currentData.index(b'\r') + 1]).encode() # usually \n or \x00
# cleanup
self.passwordState = process_backspaces(self.passwordState)
log.msg('User input password: {0}'.format(self.passwordState))
self.inputingPassword = False
# having the password (and the username, either empy or set before), we can check the login
# on the database, and if valid authenticate or else, if invalid send a fake password to get
# the login failed prompt
src_ip = self.server.transport.getPeer().host
if HoneypotPasswordChecker().checkUserPass(self.usernameState, self.passwordState, src_ip):
passwordToSend = self.backendPassword
self.authDone = True
else:
log.msg('Sending invalid auth to backend')
passwordToSend = self.backendPassword + b'fake'
# actually send to backend
self.currentData = passwordToSend + b'\r' + terminatingChar
self.sendData = True
def setProcessingStateBackend(self):
"""
This function analyses a data packet and sets the processing state of the handler accordingly.
It looks for authentication phases (password input and username input), as well as data that
may need to be processed specially.
"""
hasPassword = re.search(self.passwordPromptRegex, self.currentData)
if hasPassword:
log.msg('Password prompt from backend')
self.authStarted = True
self.inputingPassword = True
self.passwordState = b''
hasLogin = re.search(self.usernamePromptRegex, self.currentData)
if hasLogin:
log.msg('Login prompt from backend')
self.authStarted = True
self.inputingLogin = True
self.usernameState = b''
self.prePasswordData = b'\xff\xfb\x01' in self.currentData
def setProcessingStateFrontend(self):
"""
Same for the frontend.
"""
# login username is sent in channel negotiation to match the client's username
negotiationLoginPattern = re.compile(self.usernameInNegotiationRegex)
hasNegotiationLogin = negotiationLoginPattern.search(self.currentData)
if hasNegotiationLogin:
self.usernameState = hasNegotiationLogin.group(2)
log.msg('Detected username {0} in negotiation, spoofing for backend...'.format(self.usernameState.decode()))
# spoof username in data sent
# username is always sent correct, password is the one sent wrong if we don't want to authenticate
self.currentData = negotiationLoginPattern.sub(br'\1' + self.backendLogin + br'\3', self.currentData)

View File

@ -0,0 +1,123 @@
# Copyright (C) 2015, 2016 GoSecure Inc.
"""
Telnet Transport and Authentication for the Honeypot
@author: Olivier Bilodeau <obilodeau@gosecure.ca>
"""
from __future__ import absolute_import, division
import time
import uuid
from twisted.conch.telnet import TelnetTransport
from twisted.internet import reactor
from twisted.protocols.policies import TimeoutMixin
from twisted.python import log
from cowrie.core.config import CowrieConfig
from cowrie.telnet_proxy import client_transport
from cowrie.telnet_proxy.handler import TelnetHandler
class FrontendTelnetTransport(TelnetTransport, TimeoutMixin):
def __init__(self):
super().__init__()
self.peer_ip = None
self.peer_port = 0
self.local_ip = None
self.local_port = 0
self.honey_ip = CowrieConfig().get('proxy', 'backend_telnet_host')
self.honey_port = CowrieConfig().getint('proxy', 'backend_telnet_port')
self.client = None
self.frontendAuthenticated = False
self.delayedPacketsToBackend = []
self.telnetHandler = TelnetHandler(self)
def connectionMade(self):
self.transportId = uuid.uuid4().hex[:12]
sessionno = self.transport.sessionno
self.startTime = time.time()
self.setTimeout(CowrieConfig().getint('honeypot', 'authentication_timeout', fallback=120))
self.peer_ip = self.transport.getPeer().host
self.peer_port = self.transport.getPeer().port + 1
self.local_ip = self.transport.getHost().host
self.local_port = self.transport.getHost().port
# connection to the backend starts here
client_factory = client_transport.BackendTelnetFactory()
client_factory.server = self
reactor.connectTCP(self.honey_ip, self.honey_port, client_factory,
bindAddress=('0.0.0.0', 0),
timeout=10)
log.msg(eventid='cowrie.session.connect',
format='New connection: %(src_ip)s:%(src_port)s (%(dst_ip)s:%(dst_port)s) [session: %(session)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,
session=self.transportId,
sessionno='T{0}'.format(str(sessionno)),
protocol='telnet')
TelnetTransport.connectionMade(self)
def dataReceived(self, data):
self.telnetHandler.addPacket('frontend', data)
def write(self, data):
self.transport.write(data)
def timeoutConnection(self):
"""
Make sure all sessions time out eventually.
Timeout is reset when authentication succeeds.
"""
log.msg('Timeout reached in FrontendTelnetTransport')
# close transports on both sides
self.transport.loseConnection()
self.client.transport.loseConnection()
# signal that we're closing to the handler
self.telnetHandler.close()
def connectionLost(self, reason):
"""
Fires on pre-authentication disconnects
"""
self.setTimeout(None)
TelnetTransport.connectionLost(self, reason)
# close transport on backend
self.client.transport.loseConnection()
# signal that we're closing to the handler
self.telnetHandler.close()
duration = time.time() - self.startTime
log.msg(eventid='cowrie.session.closed',
format='Connection lost after %(duration)d seconds',
duration=duration)
def packet_buffer(self, payload):
"""
We have to wait until we have a connection to the backend ready. Meanwhile, we hold packets from client
to server in here.
"""
if not self.client.backendConnected:
# wait till backend connects to send packets to them
log.msg('Connection to backend not ready, buffering packet from frontend')
self.delayedPacketsToBackend.append(payload)
else:
if len(self.delayedPacketsToBackend) > 0:
self.delayedPacketsToBackend.append(payload)
else:
self.client.transport.write(payload)

View File

@ -45,7 +45,7 @@ from zope.interface import implementer, provider
import cowrie.core.checkers
import cowrie.core.realm
import cowrie.ssh.factory
import cowrie.telnet.transport
import cowrie.telnet.factory
from cowrie import core
from cowrie.core.config import CowrieConfig
from cowrie.core.utils import create_endpoint_services, get_endpoints_from_section
@ -165,7 +165,7 @@ Makes a Cowrie SSH/Telnet honeypot.
create_endpoint_services(reactor, topService, listen_endpoints, factory)
if enableTelnet:
f = cowrie.telnet.transport.HoneyPotTelnetFactory()
f = cowrie.telnet.factory.HoneyPotTelnetFactory()
f.tac = self
f.portal = portal.Portal(core.realm.HoneyPotRealm())
f.portal.registerChecker(core.checkers.HoneypotPasswordChecker())