1
0
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:
Caleb Stewart 2021-12-26 03:11:19 -05:00
parent effbe78416
commit f858441ea6
5 changed files with 95 additions and 59 deletions

View File

@ -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
View File

@ -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"},

View File

@ -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)

View File

@ -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

View File

@ -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"