mirror of https://github.com/cowrie/cowrie.git
9may (#1560)
* fix MySQL error handling * fix tar command * type hinting for proxy
This commit is contained in:
parent
988733210a
commit
ec39aad0a0
|
@ -176,7 +176,7 @@ Installing Backend Pool dependencies (OPTIONAL)
|
|||
***********************************************
|
||||
|
||||
If you want to use the proxy functionality combined with the automatic
|
||||
backend pool, you need to install some dependencies, namely qemu, libvirt,
|
||||
backend pool, you need to install some dependencies, namely QEMU, libvirt,
|
||||
and their Python interface. In Debian/Ubuntu::
|
||||
|
||||
$ sudo apt-get install qemu qemu-system-arm qemu-system-x86 libvirt-dev libvirt-daemon libvirt-daemon-system libvirt-clients nmap
|
||||
|
@ -185,7 +185,7 @@ Then install the Python API to run the backend pool::
|
|||
|
||||
(cowrie-env) $ pip install libvirt-python==6.4.0
|
||||
|
||||
To allow Qemu to use disk images and snapshots, set it to run with the user and group of the user running the pool
|
||||
To allow QEMU to use disk images and snapshots, set it to run with the user and group of the user running the pool
|
||||
(usually called 'cowrie' too::
|
||||
|
||||
$ sudo vim /etc/libvirt/qemu.conf
|
||||
|
|
2
Makefile
2
Makefile
|
@ -22,7 +22,7 @@ lint:
|
|||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf _trial_temp build dist
|
||||
rm -rf _trial_temp build dist src/_trial_temp src/Cowrie.egg-info
|
||||
make -C docs clean
|
||||
|
||||
.PHONY: pre-commit
|
||||
|
|
|
@ -43,7 +43,7 @@ Features
|
|||
|
||||
* Or proxy SSH and telnet to another system
|
||||
* Run as a pure telnet and ssh proxy with monitoring
|
||||
* Or let Cowrie manage a pool of Qemu emualted servers to provide the systems to login to
|
||||
* Or let Cowrie manage a pool of QEMU emulated servers to provide the systems to login to
|
||||
|
||||
For both settings:
|
||||
|
||||
|
|
|
@ -135,8 +135,8 @@ A set of guest (VM) parameters can be defined as we explain below:
|
|||
* **guest_image_path**: the base image upon which all VMs are created from
|
||||
|
||||
* **guest_hypervisor**: the hypervisor used; if you have an older machine or the emulated
|
||||
architecture is different from the host one, then use software-based "qemu"; however,
|
||||
if you are able to, use "kvm", it's **much** faster.
|
||||
architecture is different from the host one, then use software-based "QEMU"; however,
|
||||
if you are able to, use "KVM", it's **much** faster.
|
||||
|
||||
* **guest_memory**: memory assigned to the guest; choose a value considering the number
|
||||
of guests you'll have running in total (``pool_max_vms``)
|
||||
|
|
Binary file not shown.
|
@ -1,43 +1,43 @@
|
|||
qemu/libvirt Python examples to handle a guest
|
||||
QEMU/libvirt Python examples to handle a guest
|
||||
|
||||
# Developer Guide
|
||||
We'll start by looking at the classes that compose the Backend Pool, from "outermost" to the inner, specific classes.
|
||||
|
||||
## pool_server.py
|
||||
The main interface of the backend pool is exposed as a TCP server in _pool\_server.py_. The protocol is a very simple
|
||||
The main interface of the backend pool is exposed as a TCP server in _pool\_server.py_. The protocol is a very simple
|
||||
wire protocol, always composed of an op-code, a status code (for responses), and any needed data thereafter.
|
||||
|
||||
## pool_service.py
|
||||
The server interfaces exposes a producer-consumer infinite loop that runs on _pool\_service.py_.
|
||||
|
||||
The **producer** is an infinite loop started by the server, and runs every 5 seconds. It creates VMs up to the
|
||||
configured limit, checks which VMs become available (by testing if they accept SSH and/or Telnet connections), and
|
||||
The **producer** is an infinite loop started by the server, and runs every 5 seconds. It creates VMs up to the
|
||||
configured limit, checks which VMs become available (by testing if they accept SSH and/or Telnet connections), and
|
||||
destroys VMs that are no longer needed.
|
||||
|
||||
**Consumer** methods are called by server request, and basically involve requesting and freeing VMs. All operations on
|
||||
shared data in the producer-consumer are guarded by a lock, since there may be concurrent requests. The lock protects
|
||||
the _guests_ list, which contains references for each VM backend (in our case libvirt/qemu instances).
|
||||
**Consumer** methods are called by server request, and basically involve requesting and freeing VMs. All operations on
|
||||
shared data in the producer-consumer are guarded by a lock, since there may be concurrent requests. The lock protects
|
||||
the _guests_ list, which contains references for each VM backend (in our case libvirt/QEMU instances).
|
||||
|
||||
Since we won't be dealing with a very large number of VMs (never more than 100, we find that a single simple lock is
|
||||
Since we won't be dealing with a very large number of VMs (never more than 100, we find that a single simple lock is
|
||||
enough.
|
||||
|
||||
The Pool Service expects to find a "backend service" with a given interface:
|
||||
* A method to initialise the backend interface and environment (start_backend), stop it and destroy the current
|
||||
* A method to initialise the backend interface and environment (start_backend), stop it and destroy the current
|
||||
environment (stop_backend), and shutdown it permanently for the current execution (shutdown_backend).
|
||||
* A method to create a new guest (create_guest)
|
||||
* A method to destroy a guest (destroy_guest)
|
||||
|
||||
Currently the service supports a libvirt/qemu backend. However, by splitting the logic from generic guest handling /
|
||||
interaction with main Cowrie, from the logic to create guests in a low-level perspective, we hope to ease development
|
||||
Currently the service supports a libvirt/QEMU backend. However, by splitting the logic from generic guest handling /
|
||||
interaction with main Cowrie, from the logic to create guests in a low-level perspective, we hope to ease development
|
||||
of different kinds of backends in the future.
|
||||
|
||||
## libvirt classes
|
||||
The main class for libvirt is _backend\_service.py_, and implements the interface discussed above. Guest, network and
|
||||
The main class for libvirt is _backend\_service.py_, and implements the interface discussed above. Guest, network and
|
||||
snapshot handlers deal with those specific components of libvirt's handling.
|
||||
|
||||
Initialising libvirt involves connecting to the running system daemon, creating a network filter to restrict guest's
|
||||
Initialising libvirt involves connecting to the running system daemon, creating a network filter to restrict guest's
|
||||
Internet access, and creating a "cowrie" network in libvirt.
|
||||
|
||||
Guest creation is started by creating a snapshot from the base qcow2 image defined in the configs, and instantiating
|
||||
a guest from the XML provided. The Guest Handler replaces templates ("{guest_name}") with user configs for the wanted
|
||||
Guest creation is started by creating a snapshot from the base qcow2 image defined in the configs, and instantiating
|
||||
a guest from the XML provided. The Guest Handler replaces templates ("{guest_name}") with user configs for the wanted
|
||||
guest. If the XML provided does not contain templates, then no replacement takes place, naturally.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Copyright (c) 2019 Guilherme Borges <guilhermerosasborges@gmail.com>
|
||||
# See the COPYRIGHT file for more information
|
||||
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
|
@ -12,6 +13,8 @@ import backend_pool.libvirt.network_handler
|
|||
import backend_pool.util
|
||||
from cowrie.core.config import CowrieConfig
|
||||
|
||||
LIBVIRT_URI = "qemu:///system"
|
||||
|
||||
|
||||
class LibvirtError(Exception):
|
||||
pass
|
||||
|
@ -23,11 +26,12 @@ class LibvirtBackendService:
|
|||
import libvirt
|
||||
|
||||
# open connection to libvirt
|
||||
self.conn = libvirt.open("qemu:///system")
|
||||
self.conn = libvirt.open(LIBVIRT_URI)
|
||||
if self.conn is None:
|
||||
log.msg(
|
||||
eventid="cowrie.backend_pool.qemu",
|
||||
format="Failed to open connection to qemu:///system",
|
||||
format="Failed to open connection to %(uri)s",
|
||||
uri=LIBVIRT_URI,
|
||||
)
|
||||
raise LibvirtError()
|
||||
|
||||
|
@ -35,19 +39,19 @@ class LibvirtBackendService:
|
|||
self.network = None
|
||||
|
||||
# signals backend is ready to be operated
|
||||
self.ready = False
|
||||
self.ready: bool = False
|
||||
|
||||
# table to associate IPs and MACs
|
||||
seed = random.randint(0, sys.maxsize)
|
||||
seed: int = random.randint(0, sys.maxsize)
|
||||
self.network_table = backend_pool.util.generate_network_table(seed)
|
||||
|
||||
log.msg(
|
||||
eventid="cowrie.backend_pool.qemu", format="Connection to Qemu established"
|
||||
eventid="cowrie.backend_pool.qemu", format="Connection to QEMU established"
|
||||
)
|
||||
|
||||
def start_backend(self):
|
||||
"""
|
||||
Initialises Qemu/libvirt environment needed to run guests. Namely starts networks and network filters.
|
||||
Initialises QEMU/libvirt environment needed to run guests. Namely starts networks and network filters.
|
||||
"""
|
||||
# create a network filter
|
||||
self.filter = backend_pool.libvirt.network_handler.create_filter(self.conn)
|
||||
|
@ -62,7 +66,7 @@ class LibvirtBackendService:
|
|||
|
||||
def stop_backend(self):
|
||||
log.msg(
|
||||
eventid="cowrie.backend_pool.qemu", format="Doing Qemu clean shutdown..."
|
||||
eventid="cowrie.backend_pool.qemu", format="Doing QEMU clean shutdown..."
|
||||
)
|
||||
|
||||
self.ready = False
|
||||
|
@ -74,7 +78,7 @@ class LibvirtBackendService:
|
|||
|
||||
log.msg(
|
||||
eventid="cowrie.backend_pool.qemu",
|
||||
format="Connection to Qemu closed successfully",
|
||||
format="Connection to QEMU closed successfully",
|
||||
)
|
||||
|
||||
def get_mac_ip(self, ip_tester):
|
||||
|
|
|
@ -19,18 +19,20 @@ def create_guest(connection, mac_address, guest_unique_id):
|
|||
import libvirt
|
||||
|
||||
# get guest configurations
|
||||
configuration_file = os.path.join(
|
||||
configuration_file: str = os.path.join(
|
||||
CowrieConfig.get(
|
||||
"backend_pool", "config_files_path", fallback="share/pool_configs"
|
||||
),
|
||||
CowrieConfig.get("backend_pool", "guest_config", fallback="default_guest.xml"),
|
||||
)
|
||||
|
||||
version_tag = CowrieConfig.get("backend_pool", "guest_tag", fallback="guest")
|
||||
base_image = CowrieConfig.get("backend_pool", "guest_image_path")
|
||||
hypervisor = CowrieConfig.get("backend_pool", "guest_hypervisor", fallback="qemu")
|
||||
memory = CowrieConfig.getint("backend_pool", "guest_memory", fallback=128)
|
||||
qemu_machine = CowrieConfig.get(
|
||||
version_tag: str = CowrieConfig.get("backend_pool", "guest_tag", fallback="guest")
|
||||
base_image: str = CowrieConfig.get("backend_pool", "guest_image_path")
|
||||
hypervisor: str = CowrieConfig.get(
|
||||
"backend_pool", "guest_hypervisor", fallback="qemu"
|
||||
)
|
||||
memory: int = CowrieConfig.getint("backend_pool", "guest_memory", fallback=128)
|
||||
qemu_machine: str = CowrieConfig.get(
|
||||
"backend_pool", "guest_qemu_machine", fallback="pc-q35-3.1"
|
||||
)
|
||||
|
||||
|
@ -44,19 +46,21 @@ def create_guest(connection, mac_address, guest_unique_id):
|
|||
os._exit(1)
|
||||
|
||||
# only in some cases, like wrt
|
||||
kernel_image = CowrieConfig.get("backend_pool", "guest_kernel_image", fallback="")
|
||||
kernel_image: str = CowrieConfig.get(
|
||||
"backend_pool", "guest_kernel_image", fallback=""
|
||||
)
|
||||
|
||||
# get a directory to save snapshots, even if temporary
|
||||
try:
|
||||
# guest configuration, to be read by qemu, needs an absolute path
|
||||
snapshot_path = backend_pool.util.to_absolute_path(
|
||||
snapshot_path: str = backend_pool.util.to_absolute_path(
|
||||
CowrieConfig.get("backend_pool", "snapshot_path")
|
||||
)
|
||||
except NoOptionError:
|
||||
snapshot_path = os.getcwd()
|
||||
|
||||
# create a disk snapshot to be used by the guest
|
||||
disk_img = os.path.join(
|
||||
disk_img: str = os.path.join(
|
||||
snapshot_path, f"snapshot-{version_tag}-{guest_unique_id}.qcow2"
|
||||
)
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ def create_filter(connection):
|
|||
# lazy import to avoid exception if not using the backend_pool and libvirt not installed (#1185)
|
||||
import libvirt
|
||||
|
||||
filter_file = os.path.join(
|
||||
filter_file: str = os.path.join(
|
||||
CowrieConfig.get(
|
||||
"backend_pool", "config_files_path", fallback="share/pool_configs"
|
||||
),
|
||||
|
@ -39,7 +39,7 @@ def create_network(connection, network_table):
|
|||
import libvirt
|
||||
|
||||
# TODO support more interfaces and therefore more IP space to allow > 253 guests
|
||||
network_file = os.path.join(
|
||||
network_file: str = os.path.join(
|
||||
CowrieConfig.get(
|
||||
"backend_pool", "config_files_path", fallback="share/pool_configs"
|
||||
),
|
||||
|
@ -50,8 +50,8 @@ def create_network(connection, network_table):
|
|||
|
||||
network_xml = backend_pool.util.read_file(network_file)
|
||||
|
||||
template_host = "<host mac='{mac_address}' name='{name}' ip='{ip_address}'/>\n"
|
||||
hosts = ""
|
||||
template_host: str = "<host mac='{mac_address}' name='{name}' ip='{ip_address}'/>\n"
|
||||
hosts: str = ""
|
||||
|
||||
# generate a host entry for every possible guest in this network (253 entries)
|
||||
it = iter(network_table)
|
||||
|
|
|
@ -51,9 +51,9 @@ class ServerProtocol(protocol.Protocol):
|
|||
|
||||
|
||||
class ServerFactory(protocol.Factory):
|
||||
def __init__(self, dst_ip, dst_port):
|
||||
self.dst_ip = dst_ip
|
||||
self.dst_port = dst_port
|
||||
def __init__(self, dst_ip: str, dst_port: int) -> None:
|
||||
self.dst_ip: str = dst_ip
|
||||
self.dst_port: int = dst_port
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
return ServerProtocol(self.dst_ip, self.dst_port)
|
||||
|
|
|
@ -14,11 +14,15 @@ from cowrie.core.config import CowrieConfig
|
|||
class PoolServer(Protocol):
|
||||
def __init__(self, factory):
|
||||
self.factory = factory
|
||||
self.local_pool = CowrieConfig.get("proxy", "pool", fallback="local") == "local"
|
||||
self.pool_only = CowrieConfig.getboolean(
|
||||
self.local_pool: bool = (
|
||||
CowrieConfig.get("proxy", "pool", fallback="local") == "local"
|
||||
)
|
||||
self.pool_only: bool = CowrieConfig.getboolean(
|
||||
"backend_pool", "pool_only", fallback=False
|
||||
)
|
||||
self.use_nat = CowrieConfig.getboolean("backend_pool", "use_nat", fallback=True)
|
||||
self.use_nat: bool = CowrieConfig.getboolean(
|
||||
"backend_pool", "use_nat", fallback=True
|
||||
)
|
||||
|
||||
if self.use_nat:
|
||||
self.nat_public_ip = CowrieConfig.get("backend_pool", "nat_public_ip")
|
||||
|
|
|
@ -22,7 +22,7 @@ class PoolService:
|
|||
VM States:
|
||||
created -> available -> using -> used -> unavailable -> destroyed
|
||||
|
||||
created: initialised but not fully booted by Qemu
|
||||
created: initialised but not fully booted by QEMU
|
||||
available: can be requested
|
||||
using: a client is connected, can be served for other clients from same ip
|
||||
used: client disconnectec, but can still be served for its ip
|
||||
|
@ -38,31 +38,35 @@ class PoolService:
|
|||
self.nat_service = nat_service
|
||||
|
||||
self.guests = []
|
||||
self.guest_id = 0
|
||||
self.guest_id: int = 0
|
||||
self.guest_lock = Lock()
|
||||
|
||||
# time in seconds between each loop iteration
|
||||
self.loop_sleep_time = 5
|
||||
self.loop_sleep_time: int = 5
|
||||
self.loop_next_call = None
|
||||
|
||||
# default configs; custom values will come from the client when they connect to the pool
|
||||
self.max_vm = 2
|
||||
self.vm_unused_timeout = 600
|
||||
self.share_guests = True
|
||||
self.max_vm: int = 2
|
||||
self.vm_unused_timeout: int = 600
|
||||
self.share_guests: bool = True
|
||||
|
||||
# file configs
|
||||
self.ssh_port = CowrieConfig.getint(
|
||||
self.ssh_port: int = CowrieConfig.getint(
|
||||
"backend_pool", "guest_ssh_port", fallback=-1
|
||||
)
|
||||
self.telnet_port = CowrieConfig.getint(
|
||||
self.telnet_port: int = CowrieConfig.getint(
|
||||
"backend_pool", "guest_telnet_port", fallback=-1
|
||||
)
|
||||
|
||||
self.local_pool = CowrieConfig.get("proxy", "pool", fallback="local") == "local"
|
||||
self.pool_only = CowrieConfig.getboolean(
|
||||
self.local_pool: str = (
|
||||
CowrieConfig.get("proxy", "pool", fallback="local") == "local"
|
||||
)
|
||||
self.pool_only: bool = CowrieConfig.getboolean(
|
||||
"backend_pool", "pool_only", fallback=False
|
||||
)
|
||||
self.use_nat = CowrieConfig.getboolean("backend_pool", "use_nat", fallback=True)
|
||||
self.use_nat: bool = CowrieConfig.getboolean(
|
||||
"backend_pool", "use_nat", fallback=True
|
||||
)
|
||||
|
||||
# detect invalid config
|
||||
if not self.ssh_port > 0 and not self.telnet_port > 0:
|
||||
|
@ -72,7 +76,7 @@ class PoolService:
|
|||
)
|
||||
os._exit(1)
|
||||
|
||||
self.any_vm_up = False # TODO fix for no VM available
|
||||
self.any_vm_up: bool = False # TODO fix for no VM available
|
||||
|
||||
def start_pool(self):
|
||||
# cleanup older qemu objects
|
||||
|
@ -124,7 +128,7 @@ class PoolService:
|
|||
try:
|
||||
self.qemu.stop_backend()
|
||||
except libvirt.libvirtError:
|
||||
print("Not connected to Qemu")
|
||||
print("Not connected to QEMU")
|
||||
|
||||
def shutdown_pool(self):
|
||||
# lazy import to avoid exception if not using the backend_pool and libvirt not installed (#1185)
|
||||
|
@ -135,7 +139,7 @@ class PoolService:
|
|||
try:
|
||||
self.qemu.shutdown_backend()
|
||||
except libvirt.libvirtError:
|
||||
print("Not connected to Qemu")
|
||||
print("Not connected to QEMU")
|
||||
|
||||
def restart_pool(self):
|
||||
log.msg(
|
||||
|
@ -188,7 +192,7 @@ class PoolService:
|
|||
return has_ssh or has_telnet
|
||||
|
||||
# Producers
|
||||
def __producer_mark_timed_out(self, guest_timeout):
|
||||
def __producer_mark_timed_out(self, guest_timeout: int) -> None:
|
||||
"""
|
||||
Checks timed-out VMs and acquires lock to safely mark for deletion
|
||||
"""
|
||||
|
|
|
@ -26,7 +26,7 @@ class CommandChannel(channel.SSHChannel):
|
|||
def channelOpen(self, data):
|
||||
self.conn.sendRequest(self, "exec", common.NS(self.command), wantReply=True)
|
||||
|
||||
def dataReceived(self, data):
|
||||
def dataReceived(self, data: bytes) -> None:
|
||||
self.data += data
|
||||
|
||||
def extReceived(self, dataType, data):
|
||||
|
|
|
@ -13,20 +13,21 @@ class TelnetConnectionError(Exception):
|
|||
class TelnetClient(StatefulTelnetProtocol):
|
||||
def __init__(self):
|
||||
# output from server
|
||||
self.response = b""
|
||||
self.response: bytes = b""
|
||||
|
||||
# callLater instance to wait until we have stop getting output for some time
|
||||
self.done_callback = None
|
||||
|
||||
def connectionMade(self):
|
||||
self.command: bytes
|
||||
|
||||
def connectionMade(self):
|
||||
"""
|
||||
Set rawMode since we do not receive the login and password prompt in line mode.
|
||||
We return to default line mode when we detect the prompt in the received data stream.
|
||||
"""
|
||||
self.setRawMode()
|
||||
|
||||
def rawDataReceived(self, bytes):
|
||||
def rawDataReceived(self, data):
|
||||
"""
|
||||
The login and password prompt on some systems are not received in lineMode.
|
||||
Therefore we do the authentication in raw mode and switch back to line mode
|
||||
|
@ -38,17 +39,17 @@ class TelnetClient(StatefulTelnetProtocol):
|
|||
else:
|
||||
self.re_prompt = re.compile(self.factory.prompt.encode())
|
||||
|
||||
if re.search(br"([Ll]ogin:\s+$)", bytes):
|
||||
if re.search(br"([Ll]ogin:\s+$)", data):
|
||||
self.sendLine(self.factory.username.encode())
|
||||
elif re.search(br"([Pp]assword:\s+$)", bytes):
|
||||
elif re.search(br"([Pp]assword:\s+$)", data):
|
||||
self.sendLine(self.factory.password.encode())
|
||||
elif self.re_prompt.search(bytes):
|
||||
elif self.re_prompt.search(data):
|
||||
self.setLineMode()
|
||||
|
||||
# auth is done, send command to server
|
||||
self.send_command(self.transport.factory.command)
|
||||
|
||||
def lineReceived(self, line):
|
||||
def lineReceived(self, line: bytes) -> None:
|
||||
# ignore data sent by server before command is sent
|
||||
# ignore command echo from server
|
||||
if not self.command or line == self.command:
|
||||
|
@ -62,11 +63,11 @@ class TelnetClient(StatefulTelnetProtocol):
|
|||
|
||||
# start countdown to command done (when reached, consider the output was completely received and close)
|
||||
if not self.done_callback:
|
||||
self.done_callback = reactor.callLater(0.5, self.close)
|
||||
self.done_callback = reactor.callLater(0.5, self.close) # type: ignore
|
||||
else:
|
||||
self.done_callback.reset(0.5)
|
||||
|
||||
def send_command(self, command):
|
||||
def send_command(self, command: str) -> None:
|
||||
"""
|
||||
Sends a command via Telnet using line mode
|
||||
"""
|
||||
|
@ -113,13 +114,12 @@ class TelnetClientCommand:
|
|||
def __init__(self, callback, prompt, command):
|
||||
# callback to be called when execution is done
|
||||
self.callback = callback
|
||||
|
||||
self.prompt = prompt
|
||||
self.command = command
|
||||
|
||||
def connect(self, host, port, username, password):
|
||||
# deferred to signal command and its output is done
|
||||
done_deferred = defer.Deferred()
|
||||
done_deferred: defer.Deferred = defer.Deferred()
|
||||
|
||||
# start connection to the Telnet server
|
||||
factory = TelnetFactory(
|
||||
|
|
|
@ -18,8 +18,9 @@ class Command_tar(HoneyPotCommand):
|
|||
l, d = path.split("/"), []
|
||||
while len(l):
|
||||
d.append(l.pop(0))
|
||||
if not self.fs.exists("/".join(d)):
|
||||
self.fs.mkdir("/".join(d), 0, 0, 4096, f.mode, f.mtime)
|
||||
p = "/".join(d)
|
||||
if p and not self.fs.exists(p):
|
||||
self.fs.mkdir(p, 0, 0, 4096, f.mode, f.mtime)
|
||||
|
||||
def call(self):
|
||||
if len(self.args) < 2:
|
||||
|
|
|
@ -68,12 +68,20 @@ def convert(input):
|
|||
"""
|
||||
This converts a nested dictionary with bytes in it to string
|
||||
"""
|
||||
if isinstance(input, str):
|
||||
return input
|
||||
if isinstance(input, dict):
|
||||
return {convert(key): convert(value) for key, value in list(input.items())}
|
||||
if isinstance(input, dict):
|
||||
return {convert(key): convert(value) for key, value in list(input.items())}
|
||||
elif isinstance(input, list):
|
||||
return [convert(element) for element in input]
|
||||
elif isinstance(input, bytes):
|
||||
return input.decode("utf-8")
|
||||
try:
|
||||
string = input.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
string = repr(input)
|
||||
return string
|
||||
else:
|
||||
return input
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ class Output(cowrie.core.output.Output):
|
|||
use_unicode=True,
|
||||
)
|
||||
except (MySQLdb.Error, MySQLdb._exceptions.Error) as e:
|
||||
log.msg("output_mysql: Error %d: %s" % (e.args[0], e.args[1]))
|
||||
log.msg(f"output_mysql: Error {e.args[0]}: {e.args[1]}")
|
||||
|
||||
def stop(self):
|
||||
self.db.commit()
|
||||
|
@ -76,18 +76,18 @@ class Output(cowrie.core.output.Output):
|
|||
1146, "Table '...' doesn't exist"
|
||||
1406, "Data too long for column '...' at row ..."
|
||||
"""
|
||||
if error.value[0] in (1146, 1406):
|
||||
log.msg(f"output_mysql: MySQL Error: {error.value}")
|
||||
if error.value.args[0] in (1146, 1406):
|
||||
log.msg(f"output_mysql: MySQL Error: {error.value.args!r}")
|
||||
log.msg("MySQL schema maybe misconfigured, doublecheck database!")
|
||||
else:
|
||||
log.err(f"output_mysql: MySQL Error: {error.value}")
|
||||
log.msg(f"output_mysql: MySQL Error: {error.value.args!r}")
|
||||
|
||||
def simpleQuery(self, sql, args):
|
||||
"""
|
||||
Just run a deferred sql query, only care about errors
|
||||
"""
|
||||
if self.debug:
|
||||
log.msg(f"output_mysql: MySQL query: {sql} {repr(args)}")
|
||||
log.msg(f"output_mysql: MySQL query: {sql} {args!r}")
|
||||
d = self.db.runQuery(sql, args)
|
||||
d.addErrback(self.sqlerror)
|
||||
|
||||
|
@ -95,14 +95,14 @@ class Output(cowrie.core.output.Output):
|
|||
def write(self, entry):
|
||||
if entry["eventid"] == "cowrie.session.connect":
|
||||
r = yield self.db.runQuery(
|
||||
"SELECT `id`" "FROM `sensors`" "WHERE `ip` = %s", (self.sensor,)
|
||||
f"SELECT `id`\" \"FROM `sensors`\" \"WHERE `ip` = {self.sensor}"
|
||||
)
|
||||
|
||||
if r:
|
||||
sensorid = r[0][0]
|
||||
else:
|
||||
yield self.db.runQuery(
|
||||
"INSERT INTO `sensors` (`ip`) " "VALUES (%s)", (self.sensor,)
|
||||
f"INSERT INTO `sensors` (`ip`) \" \"VALUES ({self.sensor})"
|
||||
)
|
||||
|
||||
r = yield self.db.runQuery("SELECT LAST_INSERT_ID()")
|
||||
|
|
|
@ -11,7 +11,7 @@ from cowrie.core.config import CowrieConfig
|
|||
|
||||
class PoolClient(Protocol):
|
||||
"""
|
||||
Represents the connection between a protocol instance (SSH or Telnet) and a Qemu pool
|
||||
Represents the connection between a protocol instance (SSH or Telnet) and a QEMU pool
|
||||
"""
|
||||
|
||||
def __init__(self, factory):
|
||||
|
|
|
@ -29,7 +29,9 @@ def cowrieOpenConnectForwardingClient(remoteWindow, remoteMaxPacket, data, avata
|
|||
)
|
||||
|
||||
# Forward redirect
|
||||
redirectEnabled: bool = CowrieConfig.getboolean("ssh", "forward_redirect", fallback=False)
|
||||
redirectEnabled: bool = CowrieConfig.getboolean(
|
||||
"ssh", "forward_redirect", fallback=False
|
||||
)
|
||||
if redirectEnabled:
|
||||
redirects = {}
|
||||
items = CowrieConfig.items("ssh")
|
||||
|
@ -56,7 +58,9 @@ def cowrieOpenConnectForwardingClient(remoteWindow, remoteMaxPacket, data, avata
|
|||
)
|
||||
|
||||
# TCP tunnel
|
||||
tunnelEnabled: bool = CowrieConfig.getboolean("ssh", "forward_tunnel", fallback=False)
|
||||
tunnelEnabled: bool = CowrieConfig.getboolean(
|
||||
"ssh", "forward_tunnel", fallback=False
|
||||
)
|
||||
if tunnelEnabled:
|
||||
tunnels = {}
|
||||
items = CowrieConfig.items("ssh")
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# Copyright (c) 2019 Guilherme Borges <guilhermerosasborges@gmail.com>
|
||||
# All rights reserved.
|
||||
|
||||
from typing import Any
|
||||
|
||||
from twisted.conch.ssh import transport
|
||||
from twisted.internet import defer, protocol
|
||||
from twisted.protocols.policies import TimeoutMixin
|
||||
|
@ -173,5 +175,8 @@ class BackendSSHTransport(transport.SSHClientTransport, TimeoutMixin):
|
|||
|
||||
|
||||
class BackendSSHFactory(protocol.ClientFactory):
|
||||
|
||||
server: Any
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
return BackendSSHTransport(self)
|
||||
|
|
|
@ -28,10 +28,10 @@
|
|||
|
||||
|
||||
class BaseProtocol:
|
||||
data = b""
|
||||
packetSize = 0
|
||||
name = ""
|
||||
uuid = ""
|
||||
data: bytes = b""
|
||||
packetSize: int = 0
|
||||
name: str = ""
|
||||
uuid: str = ""
|
||||
ttylog_file = None
|
||||
|
||||
def __init__(self, uuid=None, name=None, ssh=None):
|
||||
|
@ -52,32 +52,32 @@ class BaseProtocol:
|
|||
def channel_closed(self):
|
||||
pass
|
||||
|
||||
def extract_int(self, length):
|
||||
def extract_int(self, length: int) -> int:
|
||||
value = int.from_bytes(self.data[:length], byteorder="big")
|
||||
self.packetSize = self.packetSize - length
|
||||
self.data = self.data[length:]
|
||||
return value
|
||||
|
||||
def put_int(self, number):
|
||||
def put_int(self, number: int) -> bytes:
|
||||
return number.to_bytes(4, byteorder="big")
|
||||
|
||||
def extract_string(self):
|
||||
def extract_string(self) -> bytes:
|
||||
length = self.extract_int(4)
|
||||
value = self.data[:length]
|
||||
self.packetSize -= length
|
||||
self.data = self.data[length:]
|
||||
return value
|
||||
|
||||
def extract_bool(self):
|
||||
def extract_bool(self) -> bool:
|
||||
value = self.extract_int(1)
|
||||
return bool(value)
|
||||
|
||||
def extract_data(self):
|
||||
def extract_data(self) -> bytes:
|
||||
length = self.extract_int(4)
|
||||
self.packetSize = length
|
||||
value = self.data
|
||||
self.packetSize -= len(value)
|
||||
self.data = ""
|
||||
self.data = b""
|
||||
return value
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
|
|
|
@ -52,12 +52,12 @@ class ExecTerm(base_protocol.BaseProtocol):
|
|||
self.transportId = ssh.server.transportId
|
||||
self.channelId = channelId
|
||||
|
||||
self.startTime = time.time()
|
||||
self.ttylogPath = CowrieConfig.get("honeypot", "ttylog_path")
|
||||
self.ttylogEnabled = CowrieConfig.getboolean(
|
||||
self.startTime: float = time.time()
|
||||
self.ttylogPath: str = CowrieConfig.get("honeypot", "ttylog_path")
|
||||
self.ttylogEnabled: bool = CowrieConfig.getboolean(
|
||||
"honeypot", "ttylog", fallback=True
|
||||
)
|
||||
self.ttylogSize = 0
|
||||
self.ttylogSize: bool = 0
|
||||
|
||||
if self.ttylogEnabled:
|
||||
self.ttylogFile = "{}/{}-{}-{}e.log".format(
|
||||
|
|
|
@ -26,20 +26,22 @@
|
|||
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
# SUCH DAMAGE.
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from twisted.python import log
|
||||
|
||||
from cowrie.ssh_proxy.protocols import base_protocol
|
||||
|
||||
|
||||
class SFTP(base_protocol.BaseProtocol):
|
||||
prevID = ""
|
||||
ID = ""
|
||||
handle = ""
|
||||
path = ""
|
||||
command = ""
|
||||
payloadSize = 0
|
||||
payloadOffset = 0
|
||||
theFile = ""
|
||||
prevID: str = ""
|
||||
ID: str = ""
|
||||
handle: str = ""
|
||||
path: str = ""
|
||||
command: str = ""
|
||||
payloadSize: int = 0
|
||||
payloadOffset: int = 0
|
||||
theFile: str = ""
|
||||
|
||||
packetLayout = {
|
||||
1: "SSH_FXP_INIT",
|
||||
|
@ -83,11 +85,11 @@ class SFTP(base_protocol.BaseProtocol):
|
|||
self.clientPacket = base_protocol.BaseProtocol()
|
||||
self.serverPacket = base_protocol.BaseProtocol()
|
||||
|
||||
self.parent = None
|
||||
self.parent: Optional[str] = None
|
||||
self.parentPacket = None
|
||||
self.offset = 0
|
||||
self.offset: int = 0
|
||||
|
||||
def parse_packet(self, parent, payload):
|
||||
def parse_packet(self, parent: str, payload: bytes) -> None:
|
||||
self.parent = parent
|
||||
|
||||
if parent == "[SERVER]":
|
||||
|
@ -99,7 +101,7 @@ class SFTP(base_protocol.BaseProtocol):
|
|||
self.parentPacket.packetSize = int(payload[:4].hex(), 16) - len(payload[4:])
|
||||
payload = payload[4:]
|
||||
self.parentPacket.data = payload
|
||||
payload = ""
|
||||
payload = b""
|
||||
|
||||
else:
|
||||
if len(payload) > self.parentPacket.packetSize:
|
||||
|
@ -111,7 +113,7 @@ class SFTP(base_protocol.BaseProtocol):
|
|||
else:
|
||||
self.parentPacket.packetSize -= len(payload)
|
||||
self.parentPacket.data = self.parentPacket.data + payload
|
||||
payload = ""
|
||||
payload = b""
|
||||
|
||||
if self.parentPacket.packetSize == 0:
|
||||
self.handle_packet(parent)
|
||||
|
@ -120,11 +122,12 @@ class SFTP(base_protocol.BaseProtocol):
|
|||
self.parse_packet(parent, payload)
|
||||
|
||||
def handle_packet(self, parent):
|
||||
self.packetSize = self.parentPacket.packetSize
|
||||
self.data = self.parentPacket.data
|
||||
self.packetSize: int = self.parentPacket.packetSize
|
||||
self.data: bytes = self.parentPacket.data
|
||||
self.command: bytes
|
||||
|
||||
sftp_num = self.extract_int(1)
|
||||
packet = self.packetLayout[sftp_num]
|
||||
sftp_num: int = self.extract_int(1)
|
||||
packet: str = self.packetLayout[sftp_num]
|
||||
|
||||
self.prevID = self.ID
|
||||
self.ID = self.extract_int(4)
|
||||
|
@ -252,8 +255,8 @@ class SFTP(base_protocol.BaseProtocol):
|
|||
)
|
||||
|
||||
def extract_attrs(self):
|
||||
cmd = ""
|
||||
flags = f"{self.extract_int(4):08b}"
|
||||
cmd: bytes = ""
|
||||
flags: str = f"{self.extract_int(4):08b}"
|
||||
|
||||
if flags[5] == "1":
|
||||
perms = f"{self.extract_int(4):09b}"
|
||||
|
|
|
@ -40,20 +40,20 @@ class Term(base_protocol.BaseProtocol):
|
|||
def __init__(self, uuid, chan_name, ssh, channelId):
|
||||
super().__init__(uuid, chan_name, ssh)
|
||||
|
||||
self.command = b""
|
||||
self.pointer = 0
|
||||
self.tabPress = False
|
||||
self.upArrow = False
|
||||
self.command: bytes = b""
|
||||
self.pointer: int = 0
|
||||
self.tabPress: bool = False
|
||||
self.upArrow: bool = False
|
||||
|
||||
self.transportId = ssh.server.transportId
|
||||
self.channelId = channelId
|
||||
self.transportId: int = ssh.server.transportId
|
||||
self.channelId: int = channelId
|
||||
|
||||
self.startTime = time.time()
|
||||
self.ttylogPath = CowrieConfig.get("honeypot", "ttylog_path")
|
||||
self.ttylogEnabled = CowrieConfig.getboolean(
|
||||
self.startTime: float = time.time()
|
||||
self.ttylogPath: str = CowrieConfig.get("honeypot", "ttylog_path")
|
||||
self.ttylogEnabled: bool = CowrieConfig.getboolean(
|
||||
"honeypot", "ttylog", fallback=True
|
||||
)
|
||||
self.ttylogSize = 0
|
||||
self.ttylogSize: int = 0
|
||||
|
||||
if self.ttylogEnabled:
|
||||
self.ttylogFile = "{}/{}-{}-{}i.log".format(
|
||||
|
@ -61,7 +61,7 @@ class Term(base_protocol.BaseProtocol):
|
|||
)
|
||||
ttylog.ttylog_open(self.ttylogFile, self.startTime)
|
||||
|
||||
def channel_closed(self):
|
||||
def channel_closed(self) -> None:
|
||||
if self.ttylogEnabled:
|
||||
ttylog.ttylog_close(self.ttylogFile, time.time())
|
||||
shasum = ttylog.ttylog_inputhash(self.ttylogFile)
|
||||
|
@ -87,8 +87,8 @@ class Term(base_protocol.BaseProtocol):
|
|||
duration=time.time() - self.startTime,
|
||||
)
|
||||
|
||||
def parse_packet(self, parent, payload):
|
||||
self.data = payload
|
||||
def parse_packet(self, parent: str, payload: bytes) -> None:
|
||||
self.data: bytes = payload
|
||||
|
||||
if parent == "[SERVER]":
|
||||
while len(self.data) > 0:
|
||||
|
@ -196,7 +196,7 @@ class Term(base_protocol.BaseProtocol):
|
|||
elif self.data[:3] == b"\x1b\x5b\x43":
|
||||
self.pointer += 1
|
||||
self.data = self.data[3:]
|
||||
elif self.data[:2] == b"\x1b\x5b" and self.data[3] == b"\x50":
|
||||
elif self.data[:2] == b"\x1b\x5b" and self.data[3:3] == b"\x50":
|
||||
self.data = self.data[4:]
|
||||
# Needed?!
|
||||
elif self.data[:1] != b"\x07" and self.data[:1] != b"\x0d":
|
||||
|
|
Loading…
Reference in New Issue