Merge pull request #227 from calebstewart/issue-91-paramiko-ng
Add support for SSHAgent and Key Types outside of RSA
This commit is contained in:
commit
6a913f0fb3
|
@ -11,6 +11,8 @@ and simply didn't have the time to go back and retroactively create one.
|
|||
|
||||
### Changed
|
||||
- Fixed parsing of `--ssl` argument in main entrypoint ([#225](https://github.com/calebstewart/pwncat/issues/225))
|
||||
- Replaced `paramiko` with `paramiko-ng`
|
||||
- Utilized Paramiko SSHClient which will also utilize the SSHAgent if available by default and supports key types aside from RSA ([#91](https://github.com/calebstewart/pwncat/issues/91))
|
||||
|
||||
## [0.5.1] - 2021-12-07
|
||||
Minor bug fixes. Mainly typos from changing the package name.
|
||||
|
|
|
@ -120,7 +120,7 @@ test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"]
|
|||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "3.4.7"
|
||||
version = "36.0.1"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -131,11 +131,11 @@ cffi = ">=1.12"
|
|||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
|
||||
docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
|
||||
docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
|
||||
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
|
||||
sdist = ["setuptools-rust (>=0.11.4)"]
|
||||
sdist = ["setuptools_rust (>=0.11.4)"]
|
||||
ssh = ["bcrypt (>=3.1.5)"]
|
||||
test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
|
||||
test = ["pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "docutils"
|
||||
|
@ -261,23 +261,20 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
|||
pyparsing = ">=2.0.2"
|
||||
|
||||
[[package]]
|
||||
name = "paramiko"
|
||||
version = "2.7.2"
|
||||
name = "paramiko-ng"
|
||||
version = "2.8.8"
|
||||
description = "SSH2 protocol library"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[package.dependencies]
|
||||
bcrypt = ">=3.1.3"
|
||||
cryptography = ">=2.5"
|
||||
pynacl = ">=1.0.1"
|
||||
bcrypt = ">=3"
|
||||
cryptography = ">=1.6"
|
||||
|
||||
[package.extras]
|
||||
all = ["pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "bcrypt (>=3.1.3)", "invoke (>=1.3)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"]
|
||||
ed25519 = ["pynacl (>=1.0.1)", "bcrypt (>=3.1.3)"]
|
||||
gssapi = ["pyasn1 (>=0.1.7)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"]
|
||||
invoke = ["invoke (>=1.3)"]
|
||||
ed25519 = ["pynacl"]
|
||||
gssapi = ["gssapi", "pyasn1"]
|
||||
|
||||
[[package]]
|
||||
name = "persistent"
|
||||
|
@ -787,7 +784,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "9357cb6dd124d9b201849436e710ae4baec36f65104fa505e5e155c60d602e79"
|
||||
content-hash = "23945db2620e31067d3b8810462ebaa6627fde4220ed01d49ba009e89962a725"
|
||||
|
||||
[metadata.files]
|
||||
alabaster = [
|
||||
|
@ -931,18 +928,26 @@ commonmark = [
|
|||
{file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"},
|
||||
]
|
||||
cryptography = [
|
||||
{file = "cryptography-3.4.7-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1"},
|
||||
{file = "cryptography-3.4.7-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250"},
|
||||
{file = "cryptography-3.4.7-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2"},
|
||||
{file = "cryptography-3.4.7-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6"},
|
||||
{file = "cryptography-3.4.7-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959"},
|
||||
{file = "cryptography-3.4.7-cp36-abi3-win32.whl", hash = "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d"},
|
||||
{file = "cryptography-3.4.7-cp36-abi3-win_amd64.whl", hash = "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca"},
|
||||
{file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873"},
|
||||
{file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d"},
|
||||
{file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177"},
|
||||
{file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"},
|
||||
{file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"},
|
||||
{file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b"},
|
||||
{file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:2d87cdcb378d3cfed944dac30596da1968f88fb96d7fc34fdae30a99054b2e31"},
|
||||
{file = "cryptography-36.0.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74d6c7e80609c0f4c2434b97b80c7f8fdfaa072ca4baab7e239a15d6d70ed73a"},
|
||||
{file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:6c0c021f35b421ebf5976abf2daacc47e235f8b6082d3396a2fe3ccd537ab173"},
|
||||
{file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59a9d55027a8b88fd9fd2826c4392bd487d74bf628bb9d39beecc62a644c12"},
|
||||
{file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3"},
|
||||
{file = "cryptography-36.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:94ae132f0e40fe48f310bba63f477f14a43116f05ddb69d6fa31e93f05848ae2"},
|
||||
{file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7be0eec337359c155df191d6ae00a5e8bbb63933883f4f5dffc439dac5348c3f"},
|
||||
{file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e0344c14c9cb89e76eb6a060e67980c9e35b3f36691e15e1b7a9e58a0a6c6dc3"},
|
||||
{file = "cryptography-36.0.1-cp36-abi3-win32.whl", hash = "sha256:4caa4b893d8fad33cf1964d3e51842cd78ba87401ab1d2e44556826df849a8ca"},
|
||||
{file = "cryptography-36.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:391432971a66cfaf94b21c24ab465a4cc3e8bf4a939c1ca5c3e3a6e0abebdbcf"},
|
||||
{file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bb5829d027ff82aa872d76158919045a7c1e91fbf241aec32cb07956e9ebd3c9"},
|
||||
{file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc15b1c22e55c4d5566e3ca4db8689470a0ca2babef8e3a9ee057a8b82ce4b1"},
|
||||
{file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:596f3cd67e1b950bc372c33f1a28a0692080625592ea6392987dba7f09f17a94"},
|
||||
{file = "cryptography-36.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:30ee1eb3ebe1644d1c3f183d115a8c04e4e603ed6ce8e394ed39eea4a98469ac"},
|
||||
{file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec63da4e7e4a5f924b90af42eddf20b698a70e58d86a72d943857c4c6045b3ee"},
|
||||
{file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca238ceb7ba0bdf6ce88c1b74a87bffcee5afbfa1e41e173b1ceb095b39add46"},
|
||||
{file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:ca28641954f767f9822c24e927ad894d45d5a1e501767599647259cbf030b903"},
|
||||
{file = "cryptography-36.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:39bdf8e70eee6b1c7b289ec6e5d84d49a6bfa11f8b8646b5b3dfe41219153316"},
|
||||
{file = "cryptography-36.0.1.tar.gz", hash = "sha256:53e5c1dc3d7a953de055d77bef2ff607ceef7a2aac0353b5d630ab67f7423638"},
|
||||
]
|
||||
docutils = [
|
||||
{file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"},
|
||||
|
@ -1052,9 +1057,9 @@ packaging = [
|
|||
{file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"},
|
||||
{file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"},
|
||||
]
|
||||
paramiko = [
|
||||
{file = "paramiko-2.7.2-py2.py3-none-any.whl", hash = "sha256:4f3e316fef2ac628b05097a637af35685183111d4bc1b5979bd397c2ab7b5898"},
|
||||
{file = "paramiko-2.7.2.tar.gz", hash = "sha256:7f36f4ba2c0d81d219f4595e35f70d56cc94f9ac40a6acdf51d6ca210ce65035"},
|
||||
paramiko-ng = [
|
||||
{file = "paramiko-ng-2.8.8.tar.gz", hash = "sha256:d11191bda27dea3d4cd080c41912bd286ba3879c49851b6c7fc286c2fd0d4497"},
|
||||
{file = "paramiko_ng-2.8.8-py2.py3-none-any.whl", hash = "sha256:21c8ad4d86006862ef32f6a1f68e563d3dd07fef4336b89a61d2f4c465806e8e"},
|
||||
]
|
||||
persistent = [
|
||||
{file = "persistent-4.7.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:ed939f58937f43a67a0f60abb0057e7f31e90d35b6a215159b04248368696930"},
|
||||
|
|
|
@ -8,7 +8,7 @@ An optional port argument is also accepted.
|
|||
"""
|
||||
import os
|
||||
import socket
|
||||
from typing import Optional
|
||||
from typing import Union, TextIO, Optional
|
||||
|
||||
import paramiko
|
||||
from prompt_toolkit import prompt
|
||||
|
@ -46,66 +46,32 @@ class Ssh(Channel):
|
|||
password = prompt("Password: ", is_password=True)
|
||||
|
||||
try:
|
||||
# Connect to the remote host's ssh server
|
||||
sock = socket.create_connection((host, port))
|
||||
except Exception as exc:
|
||||
raise ChannelError(self, str(exc))
|
||||
client = paramiko.client.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.client.AutoAddPolicy)
|
||||
|
||||
# Create a paramiko SSH transport layer around the socket
|
||||
t = paramiko.Transport(sock)
|
||||
try:
|
||||
t.start_client()
|
||||
except paramiko.SSHException:
|
||||
sock.close()
|
||||
raise ChannelError(self, "ssh negotiation failed")
|
||||
|
||||
if identity is not None:
|
||||
try:
|
||||
# Load the private key for the user
|
||||
if isinstance(identity, str):
|
||||
key = paramiko.RSAKey.from_private_key_file(
|
||||
os.path.expanduser(identity)
|
||||
client.connect(
|
||||
hostname=host,
|
||||
port=port,
|
||||
username=user,
|
||||
password=password,
|
||||
pkey=load_private_key(identity),
|
||||
allow_agent=True,
|
||||
look_for_keys=False,
|
||||
)
|
||||
else:
|
||||
key = paramiko.RSAKey.from_private_key(identity)
|
||||
except paramiko.ssh_exception.SSHException:
|
||||
password = prompt("RSA Private Key Passphrase: ", is_password=True)
|
||||
try:
|
||||
if isinstance(identity, str):
|
||||
key = paramiko.RSAKey.from_private_key_file(identity, password)
|
||||
else:
|
||||
# Seek back to the beginning of the file (the above load read the whole file)
|
||||
identity.seek(0)
|
||||
key = paramiko.RSAKey.from_private_key(identity, password)
|
||||
except paramiko.ssh_exception.SSHException:
|
||||
raise ChannelError(self, "invalid private key or passphrase")
|
||||
|
||||
# Attempt authentication
|
||||
try:
|
||||
t.auth_publickey(user, key)
|
||||
except paramiko.ssh_exception.AuthenticationException as exc:
|
||||
raise ChannelError(self, str(exc))
|
||||
else:
|
||||
try:
|
||||
t.auth_password(user, password)
|
||||
except paramiko.ssh_exception.AuthenticationException as exc:
|
||||
raise ChannelError(self, str(exc))
|
||||
columns, rows = os.get_terminal_size(0)
|
||||
shell = client.invoke_shell(width=columns, height=rows)
|
||||
shell.setblocking(0)
|
||||
|
||||
if not t.is_authenticated():
|
||||
t.close()
|
||||
sock.close()
|
||||
raise ChannelError(self, "authentication failed")
|
||||
|
||||
# Open an interactive session
|
||||
chan = t.open_session()
|
||||
chan.get_pty()
|
||||
chan.invoke_shell()
|
||||
chan.setblocking(0)
|
||||
|
||||
self.client = chan
|
||||
self.client = shell
|
||||
self.address = (host, port)
|
||||
self._connected = True
|
||||
|
||||
except paramiko.ssh_exception.AuthenticationException as exc:
|
||||
raise ChannelError(self, f"ssh authentication failed: {str(exc)}") from exc
|
||||
except (paramiko.ssh_exception.SSHException, socket.error) as exc:
|
||||
raise ChannelError(self, f"ssh connection failed: {str(exc)}") from exc
|
||||
|
||||
@property
|
||||
def connected(self):
|
||||
return self._connected
|
||||
|
@ -153,3 +119,36 @@ class Ssh(Channel):
|
|||
pass
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def load_private_key(identity: Union[str, TextIO], passphrase: str = None):
|
||||
"""Load a private key and return the appropriate PKey object"""
|
||||
|
||||
if identity is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
if isinstance(identity, str):
|
||||
return paramiko.pkey.load_private_key_file(
|
||||
os.path.expanduser(identity), password=passphrase
|
||||
)
|
||||
|
||||
identity.seek(0)
|
||||
return paramiko.pkey.load_private_key(identity.read(), password=passphrase)
|
||||
except paramiko.PasswordRequiredException:
|
||||
# Bad passphrase
|
||||
if passphrase is not None:
|
||||
raise
|
||||
|
||||
try:
|
||||
# No passphrase, prompt for one
|
||||
passphrase = prompt("Private Key Passphrase: ", is_password=True)
|
||||
except KeyboardInterrupt:
|
||||
passphrase = None
|
||||
|
||||
# No passphrase given, re-raise
|
||||
if passphrase is None:
|
||||
raise
|
||||
|
||||
# Try again with the given passphrase
|
||||
return load_private_key(identity, passphrase=passphrase)
|
||||
|
|
|
@ -5,9 +5,8 @@ should not generally need to use these types except as reference
|
|||
when interacting with data returned by an enumeration module.
|
||||
"""
|
||||
import time
|
||||
import pathlib
|
||||
import tempfile
|
||||
import subprocess
|
||||
from io import StringIO
|
||||
from typing import Callable, Optional
|
||||
|
||||
import rich.markup
|
||||
|
@ -356,19 +355,13 @@ class PrivateKey(Implant):
|
|||
else:
|
||||
raise ModuleFailed(f"unknown username for uid={self.uid}")
|
||||
|
||||
with tempfile.NamedTemporaryFile("w") as filp:
|
||||
filp.write(self.content)
|
||||
filp.flush()
|
||||
|
||||
pathlib.Path(filp.name).chmod(0o600)
|
||||
|
||||
try:
|
||||
# Connect via SSH
|
||||
session = manager.create_session(
|
||||
"linux",
|
||||
host=target.public_address[0],
|
||||
user=user.name,
|
||||
identity=filp.name,
|
||||
identity=StringIO(self.content + "\n"),
|
||||
)
|
||||
except (ChannelError, PlatformError) as exc:
|
||||
manager.log(
|
||||
|
|
|
@ -40,8 +40,9 @@ rich = "^10.4.0"
|
|||
python-rapidjson = "^1.0"
|
||||
ZODB3 = "^3.11.0"
|
||||
zodburi = "^2.5.0"
|
||||
paramiko = "^2.7.2"
|
||||
Jinja2 = "^3.0.1"
|
||||
paramiko-ng = "^2.8.8"
|
||||
PyNaCl = "^1.4.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
isort = "^5.8.0"
|
||||
|
|
Loading…
Reference in New Issue