mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-27 19:04:15 +01:00
Moved to paramiko-ng to provide ssh-agent support
This also provides support for key types aside from RSA
This commit is contained in:
parent
effbe78416
commit
f858441ea6
@ -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.
|
||||
|
38
poetry.lock
generated
38
poetry.lock
generated
@ -362,6 +362,22 @@ category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "pynacl"
|
||||
version = "1.4.0"
|
||||
description = "Python binding to the Networking and Cryptography (NaCl) library"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[package.dependencies]
|
||||
cffi = ">=1.4.1"
|
||||
six = "*"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"]
|
||||
tests = ["pytest (>=3.2.1,!=3.3.0)", "hypothesis (>=3.27.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "2.4.7"
|
||||
@ -768,7 +784,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "f6ff82986a82dc3982bae00fee3833362028aecba3eecf7e28a07a32db6615a5"
|
||||
content-hash = "23945db2620e31067d3b8810462ebaa6627fde4220ed01d49ba009e89962a725"
|
||||
|
||||
[metadata.files]
|
||||
alabaster = [
|
||||
@ -1158,6 +1174,26 @@ pygments = [
|
||||
{file = "Pygments-2.9.0-py3-none-any.whl", hash = "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"},
|
||||
{file = "Pygments-2.9.0.tar.gz", hash = "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f"},
|
||||
]
|
||||
pynacl = [
|
||||
{file = "PyNaCl-1.4.0-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff"},
|
||||
{file = "PyNaCl-1.4.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514"},
|
||||
{file = "PyNaCl-1.4.0-cp27-cp27m-win32.whl", hash = "sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574"},
|
||||
{file = "PyNaCl-1.4.0-cp27-cp27m-win_amd64.whl", hash = "sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80"},
|
||||
{file = "PyNaCl-1.4.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7"},
|
||||
{file = "PyNaCl-1.4.0-cp35-abi3-macosx_10_10_x86_64.whl", hash = "sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122"},
|
||||
{file = "PyNaCl-1.4.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d"},
|
||||
{file = "PyNaCl-1.4.0-cp35-abi3-win32.whl", hash = "sha256:4e10569f8cbed81cb7526ae137049759d2a8d57726d52c1a000a3ce366779634"},
|
||||
{file = "PyNaCl-1.4.0-cp35-abi3-win_amd64.whl", hash = "sha256:c914f78da4953b33d4685e3cdc7ce63401247a21425c16a39760e282075ac4a6"},
|
||||
{file = "PyNaCl-1.4.0-cp35-cp35m-win32.whl", hash = "sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4"},
|
||||
{file = "PyNaCl-1.4.0-cp35-cp35m-win_amd64.whl", hash = "sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25"},
|
||||
{file = "PyNaCl-1.4.0-cp36-cp36m-win32.whl", hash = "sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4"},
|
||||
{file = "PyNaCl-1.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6"},
|
||||
{file = "PyNaCl-1.4.0-cp37-cp37m-win32.whl", hash = "sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f"},
|
||||
{file = "PyNaCl-1.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f"},
|
||||
{file = "PyNaCl-1.4.0-cp38-cp38-win32.whl", hash = "sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96"},
|
||||
{file = "PyNaCl-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420"},
|
||||
{file = "PyNaCl-1.4.0.tar.gz", hash = "sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505"},
|
||||
]
|
||||
pyparsing = [
|
||||
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
||||
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
|
||||
|
@ -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,65 +46,31 @@ 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")
|
||||
client.connect(
|
||||
hostname=host,
|
||||
port=port,
|
||||
username=user,
|
||||
password=password,
|
||||
pkey=load_private_key(identity),
|
||||
allow_agent=True,
|
||||
look_for_keys=False,
|
||||
)
|
||||
|
||||
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)
|
||||
)
|
||||
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")
|
||||
columns, rows = os.get_terminal_size(0)
|
||||
shell = client.invoke_shell(width=columns, height=rows)
|
||||
shell.setblocking(0)
|
||||
|
||||
# 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))
|
||||
self.client = shell
|
||||
self.address = (host, port)
|
||||
self._connected = True
|
||||
|
||||
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.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):
|
||||
@ -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,8 +5,6 @@ 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
|
||||
|
@ -42,6 +42,7 @@ ZODB3 = "^3.11.0"
|
||||
zodburi = "^2.5.0"
|
||||
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
Block a user