From bf55a591bfada6ce2ca43391c784a5e9c2b98ee3 Mon Sep 17 00:00:00 2001 From: Michel Oosterhof Date: Mon, 30 Jan 2023 17:51:20 +0800 Subject: [PATCH] Devops (#1811) * update gh actions versions * update docker build/pub * docker workflow update * also update latest docker tag * ruff has taken over from flake8 --- .flake8 | 3 - .github/workflows/docker-publish.yml | 94 ------------------------ .github/workflows/docker.yml | 91 +++++++++++++++++++++++ .github/workflows/hadolint.yml | 2 +- .github/workflows/pur.yml | 4 +- .github/workflows/tox.yml | 4 +- Makefile | 2 +- pyproject.toml | 59 ++++++++++++++- requirements-dev.txt | 2 - setup.cfg | 4 - setup.py | 61 ++++----------- src/cowrie/commands/base.py | 16 ++-- src/cowrie/commands/curl.py | 6 +- src/cowrie/commands/wget.py | 11 ++- src/cowrie/core/artifact.py | 2 +- src/cowrie/core/output.py | 36 +++++---- src/cowrie/core/realm.py | 3 +- src/cowrie/output/cuckoo.py | 5 +- src/cowrie/shell/customparser.py | 2 +- src/cowrie/shell/filetransfer.py | 2 +- src/cowrie/shell/fs.py | 4 +- src/cowrie/ssh/connection.py | 2 +- src/cowrie/ssh/keys.py | 12 +-- src/cowrie/ssh/transport.py | 38 +++++----- src/cowrie/ssh/userauth.py | 14 ++-- src/cowrie/ssh_proxy/server_transport.py | 11 +-- src/cowrie/telnet/factory.py | 5 +- tox.ini | 1 - 28 files changed, 249 insertions(+), 247 deletions(-) delete mode 100644 .flake8 delete mode 100644 .github/workflows/docker-publish.yml create mode 100644 .github/workflows/docker.yml mode change 100644 => 100755 setup.py diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 98457ba8..00000000 --- a/.flake8 +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -ignore = E203, E501, W503 -max-line-length = 79 diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml deleted file mode 100644 index 505e46b0..00000000 --- a/.github/workflows/docker-publish.yml +++ /dev/null @@ -1,94 +0,0 @@ ---- -name: Docker - -on: # yamllint disable-line rule:truthy - push: - # Publish `master` as Docker `latest` image. - branches: - - master - - # Publish `v1.2.3` tags as releases. - tags: - - v* - - # Run tests for any PRs. - pull_request: - - # Run manually - workflow_dispatch: - inputs: - logLevel: - description: 'Log level' - required: true - default: 'warning' - tags: - description: 'Build and publish Docker image' - -env: - # TODO: Change variable to your image's name. - IMAGE_NAME: cowrie - -jobs: - # Run tests. - # See also https://docs.docker.com/docker-hub/builds/automated-testing/ - test: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Run tests - run: | - if [ -f docker-compose.test.yml ]; then - docker-compose --file docker-compose.test.yml build - docker-compose --file docker-compose.test.yml run sut - else - docker build --file docker/Dockerfile . - - fi - - # Push image to GitHub Packages. - # See also https://docs.docker.com/docker-hub/builds/ - push: - # Ensure test job passes before pushing image. - needs: test - - runs-on: ubuntu-latest - if: github.event_name == 'push' - - steps: - - uses: actions/checkout@v2 - - - name: Build image - run: docker build --file docker/Dockerfile --tag $IMAGE_NAME . - - - name: Login to Docker Hub - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - # TODO: Create a PAT with `read:packages` and `write:packages` scopes and save it as an Actions secret `CR_PAT` - # run: echo "${{ secrets.CR_PAT }}" | docker login https://ghcr.io -u ${{ github.actor }} --password-stdin - - - name: Push image to GitHub Container Registry - run: | - IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME - - # Change all uppercase to lowercase - IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') - - # Strip git ref prefix from version - VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') - - # Strip "v" prefix from tag name - [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') - - # Use Docker `latest` tag convention - [ "$VERSION" == "master" ] && VERSION=latest - - echo IMAGE_ID=$IMAGE_ID - echo VERSION=$VERSION - - docker tag $IMAGE_NAME $IMAGE_ID:$VERSION - docker push $IMAGE_ID:$VERSION diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 00000000..ac225e6c --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,91 @@ +--- +name: Docker Build and Publish + +on: # yamllint disable-line rule:truthy + push: + # Publish `master` as Docker `latest` image. + branches: + - master + + # Publish `v1.2.3` tags as releases. + tags: + - v* + + # Run tests for any PRs. + pull_request: + + # Run manually + workflow_dispatch: + inputs: + logLevel: + description: 'Log level' + required: true + default: 'warning' + tags: + description: 'Build and publish Docker image' + +env: + IMAGE_NAME: cowrie + +jobs: + # Push image to GitHub Packages. + # See also https://docs.docker.com/docker-hub/builds/ + push: + # Ensure test job passes before pushing image. + needs: test + + runs-on: ubuntu-latest + if: github.event_name == 'push' + + steps: + - name: Checkout + uses: actions/checkout@v3 + + # Add support for more platforms with QEMU (optional) + # https://github.com/docker/setup-qemu-action + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + with: + platform: amd64,arm64 + + # - name: Login to Docker Hub + # uses: docker/login-action@v2 + # with: + # registry: ghcr.io + # username: ${{ github.actor }} + # password: ${{ secrets.GITHUB_TOKEN }} + # # TODO: Create a PAT with `read:packages` and `write:packages` scopes and save it as an Actions secret `CR_PAT` + # # run: echo "${{ secrets.CR_PAT }}" | docker login https://ghcr.io -u ${{ github.actor }} --password-stdin + + # $(DOCKER) buildx build --platform ${PLATFORM} -t ${IMAGE}:${TAG} --build-arg BUILD_DATE=${BUILD_DATE} -f docker/Dockerfile --push . + - name: Build + uses: docker/build-push-action@v3 + with: + push: false + tags: cowrie/cowie:latest + file: docker/Dockerfile + +# - name: Push image to GitHub Container Registry +# run: | +# IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME +# +# # Change all uppercase to lowercase +# IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') +# +# # Strip git ref prefix from version +# VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') +# +# # Strip "v" prefix from tag name +# [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') +# +# # Use Docker `latest` tag convention +# [ "$VERSION" == "master" ] && VERSION=latest +# +# echo IMAGE_ID=$IMAGE_ID +# echo VERSION=$VERSION +# +# docker tag $IMAGE_NAME $IMAGE_ID:$VERSION +# docker push $IMAGE_ID:$VERSION diff --git a/.github/workflows/hadolint.yml b/.github/workflows/hadolint.yml index 1bcb9554..92721413 100644 --- a/.github/workflows/hadolint.yml +++ b/.github/workflows/hadolint.yml @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Hadolint Action uses: hadolint/hadolint-action@v3.0.0 with: diff --git a/.github/workflows/pur.yml b/.github/workflows/pur.yml index 99c7796b..11a9eeb5 100644 --- a/.github/workflows/pur.yml +++ b/.github/workflows/pur.yml @@ -22,9 +22,9 @@ jobs: - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." - name: Check out repository code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.9' - name: Install Python tools diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index add81d5d..8b97c446 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -22,9 +22,9 @@ jobs: matrix: python-version: ["3.8", "3.9", "3.10", "3.11", "pypy-3.8", "pypy-3.9"] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/Makefile b/Makefile index 6b54c648..f8b355fc 100644 --- a/Makefile +++ b/Makefile @@ -84,7 +84,7 @@ TAG=$(shell git rev-parse --short=8 HEAD) docker-build: docker/Dockerfile ## Build Docker image -$(DOCKER) buildx create --name cowrie-builder $(DOCKER) buildx use cowrie-builder - $(DOCKER) buildx build --platform ${PLATFORM} -t ${IMAGE}:${TAG} --build-arg BUILD_DATE=${BUILD_DATE} -f docker/Dockerfile --push . + $(DOCKER) buildx build --platform ${PLATFORM} -t ${IMAGE}:${TAG} -t ${IMAGE}:latest --build-arg BUILD_DATE=${BUILD_DATE} -f docker/Dockerfile --push . .PHONY: docker-run docker-run: docker-start ## Run Docker container diff --git a/pyproject.toml b/pyproject.toml index 7df51cdc..675f6e3e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,64 @@ +[build-system] +requires = ["setuptools >= 65.63"] + [project] name = "cowrie" -version = "2.4.0" +version = "2.5.0" +description="Cowrie SSH/Telnet Honeypot." +license.text="BSD-3-Clause" +authors = [ {name = "Michel Oosterhof", email="michel@oosterhof.net"}, ] +maintainers = [ {name = "Michel Oosterhof", email="michel@oosterhof.net"}, ] +keywords=["ssh", "telnet", "honeypot"] +requires-python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*, <4" +readme="README.rst" +classifiers=[ + "Development Status :: 5 - Production/Stable", + "Environment :: No Input/Output (Daemon)", + "Framework :: Twisted", + "Intended Audience :: Developers", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: BSD License", + "Operating System :: MacOS :: MacOS X", + "Operating System :: POSIX :: Linux", + "Operating System :: POSIX", + "Programming Language :: Python", + "Topic :: Security" +] +dependencies = [ + "twisted==22.10.0", + "cryptography>=0.9.1", + "configparser", + "pyopenssl", + "pyparsing", + "incremental", + "packaging", + "appdirs>=1.4.0", + "python-dateutil", + "service_identity>=14.0.0", +] + +[project.urls] +homepage = "https://www.cowrie.org/" +repository = "https://github.com/cowrie/cowrie" + +[project.scripts] +fsctl = "bin/fsctl" +asciinema = "bin/asciinema:main" +cowrie = "bin/cowrie:main" +creatfs = "bin/createfs:main" +playlog = "bin/playlog:main" + +[project.optional-dependencies] +csirtg = ["csirtgsdk==1.1.5"] +dshield = ["requests"] +elasticsearch = ["pyes"] +mysql = ["mysqlclient"] +mongodb = ["pymongo"] +rethinkdblog = ["rethinkdb"] +s3 = ["botocore"] +slack = ["slackclient"] +influxdb = ["influxdb"] [tool.isort] profile = "black" diff --git a/requirements-dev.txt b/requirements-dev.txt index b5345c03..ea1ffd65 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,8 +1,6 @@ Sphinx==5.3.0 build==0.9.0 coverage==7.0.3 -flake8-bugbear==22.12.6 -flake8==6.0.0 mypy-extensions==0.4.3; platform_python_implementation=='CPython' and python_version>'3.8' mypy-zope==0.3.11; platform_python_implementation=='CPython' and python_version>'3.8' mypy==0.981; platform_python_implementation=='CPython' and python_version>'3.8' diff --git a/setup.cfg b/setup.cfg index 0d0de9b7..609737ff 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,3 @@ [pytype] inputs = src/cowrie keep_going = True - -[flake8] -ignore = E203,E501,W503 -count = True diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index f50d5dad..8634a434 --- a/setup.py +++ b/setup.py @@ -2,60 +2,25 @@ from setuptools import setup +try: + import twisted +except ImportError: + raise SystemExit("twisted not found. Make sure you " + "have installed the Twisted core package.") + setup( - name="Cowrie", - description="Cowrie SSH/Telnet Honeypot.", - long_description="Cowrie SSH/Telnet Honeypot.", - author="Michel Oosterhof", - author_email="michel@oosterhof.net", - maintainer="Michel Oosterhof", - maintainer_email="michel@oosterhof.net", - keywords="ssh telnet honeypot", - platforms="Unix, Mac OSX", - license="BSD", - url="https://www.cowrie.org/", packages=["cowrie", "twisted"], include_package_data=True, package_dir={"": "src"}, package_data={"": ["*.md"]}, use_incremental=True, - python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*, <4", scripts=["bin/fsctl", "bin/asciinema", "bin/cowrie", "bin/createfs", "bin/playlog"], - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Environment :: No Input/Output (Daemon)", - "Framework :: Twisted", - "Intended Audience :: Developers", - "Intended Audience :: System Administrators", - "License :: OSI Approved :: BSD License", - "Operating System :: MacOS :: MacOS X", - "Operating System :: POSIX :: Linux", - "Operating System :: POSIX", - "Programming Language :: Python", - "Topic :: Security", - ], setup_requires=["incremental", "click"], - install_requires=[ - "twisted==22.10.0", - "cryptography>=0.9.1", - "configparser", - "pyopenssl", - "pyparsing", - "incremental", - "packaging", - "appdirs>=1.4.0", - "python-dateutil", - "service_identity>=14.0.0", - ], - extras_require={ - "csirtg": ["csirtgsdk==1.1.5"], - "dshield": ["requests"], - "elasticsearch": ["pyes"], - "mysql": ["mysqlclient"], - "mongodb": ["pymongo"], - "rethinkdblog": ["rethinkdb"], - "s3": ["botocore"], - "slack": ["slackclient"], - "influxdb": ["influxdb"], - }, ) + +import sys + + +def refresh_plugin_cache(): + from twisted.plugin import IPlugin, getPlugins + list(getPlugins(IPlugin)) diff --git a/src/cowrie/commands/base.py b/src/cowrie/commands/base.py index a10a75be..fece222b 100644 --- a/src/cowrie/commands/base.py +++ b/src/cowrie/commands/base.py @@ -181,7 +181,7 @@ commands["echo"] = Command_echo class Command_printf(HoneyPotCommand): def call(self) -> None: - if not len(self.args): + if not self.args: self.write("printf: usage: printf [-v var] format [arguments]\n") else: if "-v" not in self.args and len(self.args) < 2: @@ -232,7 +232,7 @@ commands["reset"] = Command_clear class Command_hostname(HoneyPotCommand): def call(self) -> None: - if len(self.args): + if self.args: if self.protocol.user.username == "root": self.protocol.hostname = self.args[0] else: @@ -249,7 +249,7 @@ class Command_ps(HoneyPotCommand): def call(self) -> None: user = self.protocol.user.username args = "" - if len(self.args): + if self.args: args = self.args[0].strip() ( _user, @@ -878,7 +878,7 @@ commands["passwd"] = Command_passwd class Command_shutdown(HoneyPotCommand): def start(self) -> None: - if len(self.args) and self.args[0].strip().count("--help"): + if self.args and self.args[0].strip().count("--help"): output = [ "Usage: shutdown [-akrhHPfnc] [-t secs] time [warning message]", "-a: use /etc/shutdown.allow ", @@ -959,7 +959,7 @@ commands["reboot"] = Command_reboot class Command_history(HoneyPotCommand): def call(self) -> None: try: - if len(self.args) and self.args[0] == "-c": + if self.args and self.args[0] == "-c": self.protocol.historyLines = [] self.protocol.historyPosition = 0 return @@ -990,7 +990,7 @@ class Command_yes(HoneyPotCommand): self.y() def y(self) -> None: - if len(self.args): + if self.args: self.write("{}\n".format(" ".join(self.args))) else: self.write("y\n") @@ -1007,7 +1007,7 @@ commands["yes"] = Command_yes class Command_sh(HoneyPotCommand): def call(self) -> None: - if len(self.args) and self.args[0].strip() == "-c": + if self.args and self.args[0].strip() == "-c": line = " ".join(self.args[1:]) @@ -1087,7 +1087,7 @@ class Command_php(HoneyPotCommand): VERSION = "PHP 5.3.5 (cli)\n" "Copyright (c) 1997-2010 The PHP Group\n" def start(self) -> None: - if len(self.args): + if self.args: if self.args[0] == "-v": self.write(Command_php.VERSION) elif self.args[0] == "-h": diff --git a/src/cowrie/commands/curl.py b/src/cowrie/commands/curl.py index c7eff144..cc6bee90 100644 --- a/src/cowrie/commands/curl.py +++ b/src/cowrie/commands/curl.py @@ -374,14 +374,14 @@ class Command_curl(HoneyPotCommand): self.protocol.logDispatch( eventid="cowrie.session.file_download", - format="Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(filename)s", + format="Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(outfile)s", url=self.url.decode(), outfile=self.artifact.shasumFilename, shasum=self.artifact.shasum, ) log.msg( eventid="cowrie.session.file_download", - format="Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(filename)s", + format="Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(outfile)s", url=self.url.decode(), outfile=self.artifact.shasumFilename, shasum=self.artifact.shasum, @@ -401,7 +401,7 @@ class Command_curl(HoneyPotCommand): log.msg( eventid="cowrie.session.file_download.failed", format="Attempt to download file(s) from URL (%(url)s) failed", - url=self.url, + url=self.url.decode(), ) if response.check(error.DNSLookupError) is not None: diff --git a/src/cowrie/commands/wget.py b/src/cowrie/commands/wget.py index c40d39d2..6a4ebc40 100644 --- a/src/cowrie/commands/wget.py +++ b/src/cowrie/commands/wget.py @@ -302,15 +302,15 @@ class Command_wget(HoneyPotCommand): self.protocol.logDispatch( eventid="cowrie.session.file_download", - format="Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(filename)s", - url=self.url, + format="Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(outfile)s", + url=self.url.decode(), outfile=self.artifact.shasumFilename, shasum=self.artifact.shasum, ) log.msg( eventid="cowrie.session.file_download", - format="Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(filename)s", - url=self.url, + format="Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(outfile)s", + url=self.url.decode(), outfile=self.artifact.shasumFilename, shasum=self.artifact.shasum, ) @@ -320,8 +320,6 @@ class Command_wget(HoneyPotCommand): """ handle errors """ - log.err(response) - if response.check(error.DNSLookupError) is not None: self.write( f"Resolving no.such ({self.host})... failed: nodename nor servname provided, or not known.\n" @@ -330,6 +328,7 @@ class Command_wget(HoneyPotCommand): self.exit() return + log.err(response) log.msg(response.printTraceback()) if hasattr(response, "getErrorMessage"): # Exceptions errormsg = response.getErrorMessage() diff --git a/src/cowrie/core/artifact.py b/src/cowrie/core/artifact.py index 6acab689..61880f51 100644 --- a/src/cowrie/core/artifact.py +++ b/src/cowrie/core/artifact.py @@ -40,7 +40,7 @@ class Artifact: def __init__(self, label: str) -> None: self.label: str = label - self.fp = tempfile.NamedTemporaryFile(dir=self.artifactDir, delete=False) + self.fp = tempfile.NamedTemporaryFile(dir=self.artifactDir, delete=False) # pylint: disable=R1732 self.tempFilename = self.fp.name self.closed: bool = False diff --git a/src/cowrie/core/output.py b/src/cowrie/core/output.py index d1a21085..09c5d2cb 100644 --- a/src/cowrie/core/output.py +++ b/src/cowrie/core/output.py @@ -58,33 +58,31 @@ from cowrie.core.config import CowrieConfig # cowrie.session.file_download # cowrie.session.file_upload -""" -The time is available in two formats in each event, as key 'time' -in epoch format and in key 'timestamp' as a ISO compliant string -in UTC. -""" + +# The time is available in two formats in each event, as key 'time' +# in epoch format and in key 'timestamp' as a ISO compliant string +# in UTC. -def convert(input): +def convert(data): """ 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): + if isinstance(data, str): + return data + if isinstance(data, dict): + return {convert(key): convert(value) for key, value in list(data.items())} + if isinstance(data, dict): + return {convert(key): convert(value) for key, value in list(data.items())} + if isinstance(data, list): + return [convert(element) for element in data] + if isinstance(data, bytes): try: - string = input.decode("utf-8") + string = data.decode("utf-8") except UnicodeDecodeError: - string = repr(input) + string = repr(data) return string - else: - return input + return data class Output(metaclass=abc.ABCMeta): diff --git a/src/cowrie/core/realm.py b/src/cowrie/core/realm.py index 119362c6..88a82576 100644 --- a/src/cowrie/core/realm.py +++ b/src/cowrie/core/realm.py @@ -44,7 +44,7 @@ class HoneyPotRealm: def __init__(self) -> None: pass - def requestAvatar(self, avatarId, mind, *interfaces): + def requestAvatar(self, avatarId, _mind, *interfaces): user: IConchUser if IConchUser in interfaces: serv = shellserver.CowrieServer(self) @@ -54,3 +54,4 @@ class HoneyPotRealm: serv = shellserver.CowrieServer(self) user = session.HoneyPotTelnetSession(avatarId, serv) return interfaces[0], user, user.logout + raise NotImplementedError diff --git a/src/cowrie/output/cuckoo.py b/src/cowrie/output/cuckoo.py index e4318a97..9297f533 100644 --- a/src/cowrie/output/cuckoo.py +++ b/src/cowrie/output/cuckoo.py @@ -48,10 +48,10 @@ class Output(cowrie.core.output.Output): """ cuckoo output """ - api_user: str api_passwd: str url_base: bytes + cuckoo_force: int def start(self): """ @@ -123,7 +123,8 @@ class Output(cowrie.core.output.Output): """ Send a file to Cuckoo """ - files = {"file": (fileName, open(artifact, "rb").read())} + with open(artifact, "rb") as art: + files = {"file": (fileName, art.read())} try: res = requests.post( urljoin(self.url_base, b"tasks/create/file"), diff --git a/src/cowrie/shell/customparser.py b/src/cowrie/shell/customparser.py index e8161c75..bffde5b3 100644 --- a/src/cowrie/shell/customparser.py +++ b/src/cowrie/shell/customparser.py @@ -36,7 +36,7 @@ class CustomParser(argparse.ArgumentParser): ): self.protocol = protocol if parents is None: - parents=[] + parents = [] super().__init__( prog=prog, usage=usage, diff --git a/src/cowrie/shell/filetransfer.py b/src/cowrie/shell/filetransfer.py index b973a2f0..63c87eb0 100644 --- a/src/cowrie/shell/filetransfer.py +++ b/src/cowrie/shell/filetransfer.py @@ -102,7 +102,7 @@ class CowrieSFTPDirectory: def __init__(self, server, directory): self.server = server self.files = server.fs.listdir(directory) - self.files = [".", ".."] + self.files + self.files = [".", "..", *self.files] self.dir = directory def __iter__(self): diff --git a/src/cowrie/shell/fs.py b/src/cowrie/shell/fs.py index e1e0bbb9..111a309a 100644 --- a/src/cowrie/shell/fs.py +++ b/src/cowrie/shell/fs.py @@ -432,9 +432,7 @@ class HoneyPotFilesystem: return True return False - """ - Below additions for SFTP support, try to keep functions here similar to os.* - """ + # Below additions for SFTP support, try to keep functions here similar to os.* def open(self, filename: str, openFlags: int, mode: int) -> Optional[int]: """ diff --git a/src/cowrie/ssh/connection.py b/src/cowrie/ssh/connection.py index 055bc2de..9a2455ce 100644 --- a/src/cowrie/ssh/connection.py +++ b/src/cowrie/ssh/connection.py @@ -65,4 +65,4 @@ class CowrieSSHConnection(connection.SSHConnection): if wantReply: d.addCallback(self._cbChannelRequest, localChannel) d.addErrback(self._ebChannelRequest, localChannel) - return d + return d diff --git a/src/cowrie/ssh/keys.py b/src/cowrie/ssh/keys.py index 1e05a66e..a33b4499 100644 --- a/src/cowrie/ssh/keys.py +++ b/src/cowrie/ssh/keys.py @@ -9,6 +9,12 @@ from __future__ import annotations import os +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.asymmetric import dsa +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey + from twisted.conch.ssh import keys from twisted.python import log @@ -20,8 +26,6 @@ def getRSAKeys(): privateKeyFile: str = CowrieConfig.get("ssh", "rsa_private_key") if not (os.path.exists(publicKeyFile) and os.path.exists(privateKeyFile)): log.msg("Generating new RSA keypair...") - from cryptography.hazmat.backends import default_backend - from cryptography.hazmat.primitives.asymmetric import rsa rsaKey = rsa.generate_private_key( public_exponent=65537, key_size=2048, backend=default_backend() @@ -45,8 +49,6 @@ def getDSAKeys(): privateKeyFile: str = CowrieConfig.get("ssh", "dsa_private_key") if not (os.path.exists(publicKeyFile) and os.path.exists(privateKeyFile)): log.msg("Generating new DSA keypair...") - from cryptography.hazmat.backends import default_backend - from cryptography.hazmat.primitives.asymmetric import dsa dsaKey = dsa.generate_private_key(key_size=1024, backend=default_backend()) publicKeyString = keys.Key(dsaKey).public().toString("openssh") @@ -68,7 +70,6 @@ def getECDSAKeys(): privateKeyFile: str = CowrieConfig.get("ssh", "ecdsa_private_key") if not (os.path.exists(publicKeyFile) and os.path.exists(privateKeyFile)): log.msg("Generating new ECDSA keypair...") - from cryptography.hazmat.primitives.asymmetric import ec ecdsaKey = ec.generate_private_key(ec.SECP256R1()) publicKeyString = keys.Key(ecdsaKey).public().toString("openssh") @@ -90,7 +91,6 @@ def geted25519Keys(): privateKeyFile: str = CowrieConfig.get("ssh", "ed25519_private_key") if not (os.path.exists(publicKeyFile) and os.path.exists(privateKeyFile)): log.msg("Generating new ed25519 keypair...") - from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey ed25519Key = Ed25519PrivateKey.generate() publicKeyString = keys.Key(ed25519Key).public().toString("openssh") diff --git a/src/cowrie/ssh/transport.py b/src/cowrie/ssh/transport.py index fbc500d2..11ee18ca 100644 --- a/src/cowrie/ssh/transport.py +++ b/src/cowrie/ssh/transport.py @@ -29,6 +29,7 @@ class HoneyPotSSHTransport(transport.SSHServerTransport, TimeoutMixin): startTime: float = 0.0 gotVersion: bool = False buf: bytes + transportId: str ipv4rex = re.compile(r"^::ffff:(\d+\.\d+\.\d+\.\d+)$") auth_timeout: int = CowrieConfig.getint( "honeypot", "authentication_timeout", fallback=120 @@ -57,7 +58,7 @@ class HoneyPotSSHTransport(transport.SSHServerTransport, TimeoutMixin): """ self.buf = b"" - self.transportId: str = uuid.uuid4().hex[:12] + self.transportId = uuid.uuid4().hex[:12] src_ip: str = self.transport.getPeer().host ipv4_search = self.ipv4rex.search(src_ip) @@ -112,42 +113,39 @@ class HoneyPotSSHTransport(transport.SSHServerTransport, TimeoutMixin): if not self.gotVersion: if b"\n" not in self.buf: return - self.otherVersionString = self.buf.split(b"\n")[0].strip() + otherVersion: bytes = self.buf.split(b"\n")[0].strip() log.msg( eventid="cowrie.client.version", - version=self.otherVersionString.decode( + version=otherVersion.decode( "utf-8", errors="backslashreplace" ), format="Remote SSH version: %(version)s", ) - m = re.match(rb"SSH-(\d+.\d+)-(.*)", self.otherVersionString) + m = re.match(rb"SSH-(\d+.\d+)-(.*)", otherVersion) if m is None: log.msg( - "Bad protocol version identification: {}".format( - repr(self.otherVersionString) - ) + f"Bad protocol version identification: {repr(otherVersion)}" ) # OpenSSH sending the same message self.transport.write(b"Invalid SSH identification string.\n") self.transport.loseConnection() return - else: - self.gotVersion = True - remote_version = m.group(1) - if remote_version not in self.supportedVersions: - self._unsupportedVersionReceived(self.otherVersionString) - return - i = self.buf.index(b"\n") - self.buf = self.buf[i + 1 :] - self.sendKexInit() + self.gotVersion = True + remote_version = m.group(1) + if remote_version not in self.supportedVersions: + self._unsupportedVersionReceived(otherVersion) + return + i = self.buf.index(b"\n") + self.buf = self.buf[i + 1 :] + self.sendKexInit() packet = self.getPacket() while packet: messageNum = ord(packet[0:1]) self.dispatchMessage(messageNum, packet[1:]) packet = self.getPacket() - def dispatchMessage(self, message_num: int, payload: bytes) -> None: - transport.SSHServerTransport.dispatchMessage(self, message_num, payload) + def dispatchMessage(self, messageNum: int, payload: bytes) -> None: + transport.SSHServerTransport.dispatchMessage(self, messageNum, payload) def sendPacket(self, messageType: int, payload: bytes) -> None: """ @@ -195,9 +193,7 @@ class HoneyPotSSHTransport(transport.SSHServerTransport, TimeoutMixin): cencCS = ",".join([alg.decode("utf-8") for alg in encCS]) cmacCS = ",".join([alg.decode("utf-8") for alg in macCS]) ccompCS = ",".join([alg.decode("utf-8") for alg in compCS]) - hasshAlgorithms = "{kex};{enc};{mac};{cmp}".format( - kex=ckexAlgs, enc=cencCS, mac=cmacCS, cmp=ccompCS - ) + hasshAlgorithms = f"{ckexAlgs};{cencCS};{cmacCS};{ccompCS}" hassh = md5(hasshAlgorithms.encode("utf-8")).hexdigest() log.msg( diff --git a/src/cowrie/ssh/userauth.py b/src/cowrie/ssh/userauth.py index 5ad018a1..47033f4d 100644 --- a/src/cowrie/ssh/userauth.py +++ b/src/cowrie/ssh/userauth.py @@ -28,8 +28,8 @@ class HoneyPotSSHUserAuthServer(userauth.SSHUserAuthServer): * IP based authentication """ + bannerSent: bool = False user: str - _pamDeferred: defer.Deferred | None def serviceStarted(self) -> None: @@ -43,7 +43,6 @@ class HoneyPotSSHUserAuthServer(userauth.SSHUserAuthServer): self.interfaceToMethod[ credentials.IPluggableAuthenticationModulesIP ] = b"keyboard-interactive" - self.bannerSent: bool = False self._pamDeferred: defer.Deferred | None = None userauth.SSHUserAuthServer.serviceStarted(self) @@ -57,10 +56,11 @@ class HoneyPotSSHUserAuthServer(userauth.SSHUserAuthServer): self.bannerSent = True try: issuefile = CowrieConfig.get("honeypot", "contents_path") + "/etc/issue.net" - data = open(issuefile).read() + with open(issuefile, "rb") as issue: + data = issue.read() except OSError: return - if not data or not len(data.strip()): + if not data or not data.strip(): return self.transport.sendPacket(userauth.MSG_USERAUTH_BANNER, NS(data) + NS(b"en")) @@ -84,7 +84,7 @@ class HoneyPotSSHUserAuthServer(userauth.SSHUserAuthServer): # return defer.fail(error.ConchError("Incorrect signature")) # return userauth.SSHUserAuthServer.auth_publickey(self, packet) - def auth_none(self, packet: bytes) -> Any: + def auth_none(self, _packet: bytes) -> Any: """ Allow every login """ @@ -101,7 +101,7 @@ class HoneyPotSSHUserAuthServer(userauth.SSHUserAuthServer): c = credentials.UsernamePasswordIP(self.user, password, srcIp) return self.portal.login(c, srcIp, IConchUser).addErrback(self._ebPassword) - def auth_keyboard_interactive(self, packet: bytes) -> Any: + def auth_keyboard_interactive(self, _packet: bytes) -> Any: """ Keyboard interactive authentication. No payload. We create a PluggableAuthenticationModules credential and authenticate with our @@ -142,7 +142,7 @@ class HoneyPotSSHUserAuthServer(userauth.SSHUserAuthServer): elif kind in (3, 4): return defer.fail(error.ConchError("cannot handle PAM 3 or 4 messages")) else: - return defer.fail(error.ConchError("bad PAM auth kind %i" % (kind,))) + return defer.fail(error.ConchError(f"bad PAM auth kind {kind}" )) packet = NS(b"") + NS(b"") + NS(b"") packet += struct.pack(">L", len(resp)) for prompt, echo in resp: diff --git a/src/cowrie/ssh_proxy/server_transport.py b/src/cowrie/ssh_proxy/server_transport.py index 3d3b3cde..ba082b2d 100644 --- a/src/cowrie/ssh_proxy/server_transport.py +++ b/src/cowrie/ssh_proxy/server_transport.py @@ -102,7 +102,6 @@ class FrontendSSHTransport(transport.SSHServerTransport, TimeoutMixin): b"none", b"none", b"none", b"none" ) self.currentEncryptions.setKeys(b"", b"", b"", b"", b"", b"") - self.otherVersionString: bytes = b"Unknown" log.msg( eventid="cowrie.session.connect", @@ -212,20 +211,18 @@ class FrontendSSHTransport(transport.SSHServerTransport, TimeoutMixin): if not self.gotVersion: if b"\n" not in self.buf: return - self.otherVersionString = self.buf.split(b"\n")[0].strip() + otherVersion = self.buf.split(b"\n")[0].strip() log.msg( eventid="cowrie.client.version", - version=self.otherVersionString.decode( + version=otherVersion.decode( "utf-8", errors="backslashreplace" ), format="Remote SSH version: %(version)s", ) - m = re.match(rb"SSH-(\d+.\d+)-(.*)", self.otherVersionString) + m = re.match(rb"SSH-(\d+.\d+)-(.*)", otherVersion) if m is None: log.msg( - "Bad protocol version identification: {}".format( - repr(self.otherVersionString) - ) + f"Bad protocol version identification: {repr(otherVersion)}" ) if self.transport: self.transport.write(b"Protocol mismatch.\n") diff --git a/src/cowrie/telnet/factory.py b/src/cowrie/telnet/factory.py index 40903f25..b2ccca4b 100644 --- a/src/cowrie/telnet/factory.py +++ b/src/cowrie/telnet/factory.py @@ -28,6 +28,8 @@ class HoneyPotTelnetFactory(protocol.ServerFactory): tac: IPlugin portal: tp.Portal | None = None # gets set by Twisted plugin + banner: bytes + starttime: float def __init__(self, backend, pool_handler): self.backend: str = backend @@ -47,7 +49,8 @@ class HoneyPotTelnetFactory(protocol.ServerFactory): try: honeyfs = CowrieConfig.get("honeypot", "contents_path") issuefile = honeyfs + "/etc/issue.net" - self.banner = open(issuefile, "rb").read() + with open(issuefile, "rb") as banner: + self.banner = banner.read() except OSError: self.banner = b"" diff --git a/tox.ini b/tox.ini index ea6e3250..e5a61be7 100644 --- a/tox.ini +++ b/tox.ini @@ -32,7 +32,6 @@ allowlist_externals = yamllint commands = ruff {toxinidir}/src - - flake8 --ignore E203,E501,W503 --count --statistics {toxinidir}/src - yamllint {toxinidir} - pylint {toxinidir}/src basepython = python3.10