Skip to content

SSH: catch TimeoutError during login with a clean fail message - #1428

Open
MRX-72 wants to merge 1 commit into
Pennyw0rth:mainfrom
MRX-72:fix-ssh-timeout-error
Open

MRX-72 wants to merge 1 commit into
Pennyw0rth:mainfrom
MRX-72:fix-ssh-timeout-error

Conversation

@MRX-72

@MRX-72 MRX-72 commented Sep 18, 2026

Copy link
Copy Markdown

Description

plaintext_login() in nxc/protocols/ssh.py caught AuthenticationException and SSHException, then fell through to the bare except Exception handler, which calls logger.exception() and dumps a full traceback. A connection timing out mid-login is routine on a sweep, not something that warrants a stack trace on the console — this is the noise shown in the screenshot on #358.

This adds one except TimeoutError arm before the generic handler, logging a single fail line instead. smb.py already handles timeouts this way (nxc/protocols/smb.py:976), so this follows the existing pattern rather than inventing one.

No new dependencies.

AI disclosure: written with Claude Code (Opus 5), heavily AI-assisted — the model located the handler chain, wrote the one-line change and the verification harness below. I reviewed the diff myself, chose to match the existing smb.py pattern rather than a new one, and ran the before/after verification. Fixes an issue opened by a maintainer (#358), not a drive-by.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Deprecation of feature or functionality
  • This change requires a documentation update
  • This requires a third party update (such as Impacket, Dploot, lsassy, etc)
  • This PR was created with the assistance of AI (list what type of assistance, tool(s)/model(s) in the description)

Setup guide for the review

Environment: macOS 26.3, Python 3.13, paramiko 5.0.0.

Reproducing the bug needs nothing more than an SSH target that swallows packets. Point nxc at a non-routable address so the connect times out rather than being refused:

netexec ssh 10.255.255.1 -u user -p pass --ssh-timeout 3

A refused connection (ECONNREFUSED) will not reproduce it — that path raises SSHException, which was already handled. It has to be a genuine timeout.

What I ran. Full nxc does not install on my machine — aardwolf needs a Rust toolchain to build — so I drove the real plaintext_login() directly instead: extracted the function from nxc/protocols/ssh.py and executed it with a real paramiko.SSHClient against 10.255.255.1:22, with a 3 s timeout. The timeout is genuine paramiko behaviour, not a raise I stubbed in, and it runs through the actual except-chain in the source file. The same harness run against origin/main and against this branch:

BEFORE (origin/main)
  logger.exception(TimeoutError('timed out'))      <- the traceback dump from #358

AFTER (this branch)
  logger.fail('user:pass Connection timed out')

Both return False, as before.

To be straight about the limits of that: I have verified the timeout path end-to-end with real paramiko, but I have not run the full netexec ssh CLI against a live SSH server, so the surrounding sweep output is unconfirmed on my side. Everything else in the function is untouched.

Screenshots (if appropriate):

None — the before/after output is quoted above rather than screenshotted.

Checklist:

  • I have ran Ruff against my changes (poetry: poetry run ruff check ., use --fix to automatically fix what it can)
  • I have added or updated the tests/e2e_commands.txt file if necessary (new modules or features are required to be added to the e2e tests)
  • If reliant on changes of third party dependencies, such as Impacket, dploot, lsassy, etc, I have linked the relevant PRs in those projects
  • I have linked relevant sources that describes the added technique (blog posts, documentation, etc)
  • I have performed a self-review of my own code (not an AI review)
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (PR here: https://github.com/Pennyw0rth/NetExec-Wiki)

Notes on the unchecked boxes: no e2e_commands.txt entry since this adds no command or module, only changes how an existing failure is reported; no third-party deps; no wiki change, as no documented behaviour changes. The one-line handler needed no comment beyond the message itself — "linked relevant sources" is the existing smb.py:976 precedent cited above.

Fixes #358

plaintext_login() only caught AuthenticationException and SSHException
before falling through to the generic Exception handler, which logs via
logger.exception() and dumps a full traceback. A connection timing out
mid-login is routine on a target sweep, not something that needs a stack
trace, and smb.py already treats TimeoutError this way elsewhere in the
codebase.

Fixes Pennyw0rth#358
@github-actions

Copy link
Copy Markdown

It looks like the PR template may not have been filled out. The following sections appear to be missing:

  • Description

  • Type of change

  • Setup guide for the review

  • Checklist

Please edit your PR description to include them. The template helps reviewers understand and test your changes. Thanks!

@MRX-72

MRX-72 commented Sep 18, 2026

Copy link
Copy Markdown
Author

Template filled in — the bot comment above is stale (the check re-ran green on the edit, it just doesn't clear its own comment). AI usage is disclosed in the description per AI_POLICY.md, and #358 was opened by a maintainer.

One thing worth flagging for the reviewer: I verified the timeout path with real paramiko against a blackholed host, but not through the full netexec ssh CLI — aardwolf won't build here without a Rust toolchain. Details and the before/after output are in the description.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SSH: Catch timeout error

1 participant