1
0
mirror of https://github.com/jtesta/ssh-audit.git synced 2025-07-06 14:02:49 -05:00

312 Commits

Author SHA1 Message Date
f96c0501e9 Bumped version number. 2021-02-23 20:39:18 -05:00
446a411424 Added build_windows_executable.sh. 2021-02-23 19:54:12 -05:00
b300ad1252 Refactored IPv4/6 preference logic to fix pylint warnings. 2021-02-23 16:05:01 -05:00
1bbc3feb57 Added OpenSSH 8.5 built-in policy. Added sntrup761x25519-sha512@openssh.com kex. 2021-02-23 16:02:20 -05:00
8f9771c4e6 Added markdown to PACKAGING. 2021-02-23 09:46:58 -05:00
8a8c284d9a Colour no longer disabled on older vers of Windows. If ssh-audit invoked with a manual parameter and the colorama library was not imported then colour output is disabled. (#95) 2021-02-18 14:52:08 -05:00
1b7cfbec71 Disable color output on Windows 8 and Windows Server 2012. 2021-02-06 11:03:39 -05:00
3c0fc8ead4 Updated README. 2021-02-05 22:12:27 -05:00
ef831d17e0 When -n/--no-colors is used, strip out color from Windows man page. 2021-02-05 21:45:56 -05:00
36094611ce Fixed unicode errors when printing the man page on Windows. 2021-02-05 20:39:12 -05:00
49cf91a902 No longer ignoring mypy and pylint results. 2021-02-05 16:26:14 -05:00
11e2e77585 Simplified Windows man page processing. Added Cygwin support to update_windows_man_page.sh. 2021-02-05 16:25:04 -05:00
090b5d760b Man Page on Windows (#93)
* Man Page on Windows

* Corrected typo in update_windows_man_page.sh

* Check that the 'sed' (stream editor) binary exists
2021-02-05 15:43:50 -05:00
7878d66a46 Now using Python 3.9 base image. 2021-02-02 13:25:52 -05:00
730d6904c2 Updated README. 2021-02-02 12:22:50 -05:00
e0f0956edc Added extra warnings for SSHv1. (#6) 2021-02-02 12:20:37 -05:00
d42725652f Updated README. 2021-02-02 09:54:10 -05:00
6b67a2efb3 Add your local server config to .gitignore (#84) 2021-02-01 19:26:57 -05:00
c49a0fb22f Upgraded SHA-1 key signatures from warnings to failures. Added deprecation warning to ssh-rsa-cert-v00@openssh.com, ssh-rsa-cert-v01@openssh.com, x509v3-sign-rsa, and x509v3-ssh-rsa host key types. 2021-02-01 19:19:46 -05:00
dbe14a075e Added future deprecation notice of ssh-rsa (#92) 2021-02-01 13:17:46 -05:00
13d15baa2a Added multi-threaded scanning support. 2021-02-01 13:10:06 -05:00
bbb81e24ab Streamlined sending of KEXINIT messages. 2021-01-21 11:23:40 -05:00
bbbd75ee69 Tox will now fail on pylint or typing problems. 2021-01-21 10:47:52 -05:00
60de5e55cb Transformed comment type annotations to variable declaration annotations. 2021-01-21 10:20:48 -05:00
4e2f9da632 Updated README. 2021-01-21 07:53:09 -05:00
287c551ff8 Removed Python 3.5 support. 2021-01-20 20:47:26 -05:00
d9a4b49560 Removed Python 3.5 support. Added ARM64 testing in Travis. 2021-01-20 15:58:48 -05:00
a4c78512d8 Add support to ppc64le (#88) 2021-01-20 15:54:55 -05:00
1ba4c7c7ca Send KEX before reading server's KEX during host key and GEX tests; this prevents deadlock against certain server implementations. 2021-01-20 15:27:38 -05:00
338ffc5adb Fixed crash when receiving unexpected response during host key test. 2020-11-05 20:29:39 -05:00
52d1e8f27b Fixed pylint warning. 2020-11-05 20:28:14 -05:00
00dc22b00b Delete output directory only upon successful run to make debugging easier. 2020-11-05 20:25:34 -05:00
0d9881966c Added version check for OpenSSH user enumeration (CVE-2018-15473). (#83) 2020-11-05 20:24:09 -05:00
5c8dc5105b Bumped version number. 2020-11-05 20:16:35 -05:00
75be333bd2 Updated packaging instructions and merged Windows instructions. 2020-10-28 21:01:47 -04:00
81ae0eb8f7 Bumped version. 2020-10-28 19:25:11 -04:00
efec566382 Now testing with stable version of Python 3.9. (#77) 2020-10-28 13:04:09 -04:00
edbbad5aee Updated README. 2020-10-28 12:03:37 -04:00
a3e4f9dbaa Added similar algorithm suggestions to --lookup (#80) 2020-10-28 11:56:12 -04:00
c2da269f06 Added missing tests. 2020-10-21 19:40:22 -04:00
0cb3127482 Fixed pylint warnings. 2020-10-21 19:36:43 -04:00
85c0f854e3 Added Travis status. 2020-10-21 19:36:00 -04:00
f0db035044 Now prints a graceful error message when policy file is not found. 2020-10-20 23:26:21 -04:00
1730126af8 Removed 'ssh-rsa-cert-v01@openssh.com' from built-in policies. 2020-10-20 23:19:56 -04:00
175bd2cf66 Fixed recommendation output function from suppressing some algorithms inappropriately. 2020-10-20 21:34:34 -04:00
53300047e5 Docker testing now continues regardless of failures (makes fixing multiple broken tests much easier). 2020-10-20 21:26:06 -04:00
619efc7349 Flag 'ssh-rsa-cert-v01@openssh.com' as unsafe due to SHA-1 hash. 2020-10-20 17:39:34 -04:00
ec48249deb Now reports policy errors in an easier to read format. (#63) 2020-10-20 16:25:39 -04:00
ec76dac2fc Suppressed pylint warning. 2020-10-20 16:21:56 -04:00
1acfb01e61 Updated Snap instructions & Makefile.snap. 2020-10-20 15:30:04 -04:00
f893a8031f Updated PyPI packaging instructions & Makefile.pypi. 2020-10-20 14:04:14 -04:00
240b705d61 OpenSSH-portable patch level 1 now considered equivalent to stock OpenBSD version. 2020-10-20 13:17:32 -04:00
17780ff194 Added support for building official docker images. (#76) 2020-10-20 11:31:50 -04:00
83d8014a50 Fixed OpenSSH patch version comparison. (#74) 2020-10-19 18:49:52 -04:00
2bb31b306f Added Python 3.9-dev testing to Tox and Travis. 2020-10-19 18:01:17 -04:00
8fa3a12057 Parse public key sizes for 'rsa-sha2-256-cert-v01@openssh.com' and 'rsa-sha2-512-cert-v01@openssh.com' host key types. Include expected CA key sizes in built-in policies. 2020-10-19 17:42:12 -04:00
046c866da4 Moved built-in policies from external files to internal database. (#75) 2020-10-19 17:27:37 -04:00
2a7b9292bb Updated README.md 2020-10-15 20:36:08 -04:00
fa488e25a3 Added pylint exclusions. 2020-10-15 15:04:16 -04:00
1a5c0e7fad Split ssh_audit.py into separate files (#47). 2020-10-15 14:34:23 -04:00
e9df9ee45c Updated README. 2020-10-11 14:44:28 -04:00
6497213900 Fixed docker tests. 2020-10-11 14:41:58 -04:00
b15664929f Improve PyPI packaging (#71)
* Move files for better setup.py packaging

* Update setup.py and configs for src layout

* Run tests on setup.py build

In effect, this tests that the setup.py configuration is correct.

coverage combine and coverage:paths are added to keep the displayed
coverage paths as src/ssh_audit/*.py instead of
.tox/$envname/**/site-packages/ssh_audit/*.py

* Remove unnecessary encoding declarations

Python 3 defaults to UTF-8 encoding.
https://docs.python.org/3/reference/lexical_analysis.html#encoding-declarations

* Remove shebang from colorama type stubs

Shouldn't need to be an executable.
Related: git has this file tracked as chmod -x.
2020-10-11 14:03:02 -04:00
1b0446344c Deleted deepsource config. (#70) 2020-10-01 20:04:54 -04:00
cd58a6180f Remove unused variables (#68)
When you get multiple values from unpacking, and you do not need all of
them, there is the convention to assign `_` to the unused ones.

modified:   ssh-audit.py
2020-10-01 19:48:07 -04:00
ca4ebc56f9 Docker images are now pulled from Dockerhub by default. 2020-10-01 19:42:48 -04:00
2a87860e84 Added 1 new cipher: des-cbc@ssh.com. Bumped version. 2020-09-29 15:03:41 -04:00
6067da6793 Updated packaging notes and snap build process. 2020-09-29 10:24:36 -04:00
c8a1db33c8 Fixed pylint warnings regarding bad indendation. 2020-09-28 13:56:39 -04:00
32684ddc84 Removed reference to deleted dev branch. 2020-09-28 13:51:18 -04:00
da4f114b9c Updated Windows build instructions. 2020-09-27 19:32:23 -04:00
dc0a959402 Use brighter colors on Windows for better readability. Disable unicode characters on Windows since the default terminal does not display them properly. 2020-09-27 19:29:29 -04:00
eb1588ddc7 Added release date for v2.3.0. Added link for policy tutorial. 2020-09-27 17:12:10 -04:00
b7d698d743 Added policy for hardened OpenSSH v8.4. 2020-09-27 17:04:43 -04:00
b0c00749a6 Improved formatting of usage examples. Added link to web front-end. 2020-09-27 13:24:37 -04:00
6e3e8bac74 Added policy audit examples and additional usage examples. 2020-09-27 13:13:38 -04:00
632adc076a Policy check output now prints port number, if applicable. 2020-09-27 11:48:15 -04:00
13b065b316 Added CONTRIBUTING.md (#54). 2020-09-26 22:06:49 -04:00
a7581e07dc Added explicit statement regarding fork (#58). 2020-09-26 20:14:29 -04:00
4cae6aff43 Added 6 new host key types: 'spi-sign-rsa', 'ssh-ed448', 'x509v3-ecdsa-sha2-nistp256', 'x509v3-ecdsa-sha2-nistp384', 'x509v3-ecdsa-sha2-nistp521', 'x509v3-rsa2048-sha256'. Added 5 new key exchanges: 'gss-group14-sha256-', 'gss-group15-sha512-', 'gss-group16-sha512-', 'gss-nistp256-sha256-', 'gss-curve25519-sha256-'. 2020-09-26 19:32:19 -04:00
3e20f7c622 Fixed optional host key values. 2020-08-12 15:26:18 -04:00
1123ac718c Send peer a list of supported algorithms after the banner exchange. Fixes not only the weird case of an ssh-audit client hanging against an ssh-audit server, but perhaps some real-world hangs as well. 2020-08-11 20:11:42 -04:00
6d84cfdc31 Updated program return values for various connection error instances and unknown errors. 2020-08-11 19:45:59 -04:00
c7ad1828d8 Fixed return value processing and mypy warning in algorithm_lookup(). Updated help listing, man page, and README. 2020-08-11 19:28:53 -04:00
86cb453928 Algorithm lookup (#53)
* Adding ssh-audit.py to algorithm_lookup_branch

* Removed the use of an error handler from algorithm_lookup and implemented suggestions made by jugmac00 and jtesta
2020-08-11 19:02:35 -04:00
0c00b37328 Added .deepsource.toml for DeepSource integration. 2020-07-30 12:08:18 -04:00
936acfa37d Added more structure to JSON result when policy errors are found. 2020-07-29 12:36:08 -04:00
b5d7f73125 When an unexpected exit code is returned, print more debugging info. 2020-07-29 12:31:24 -04:00
6a7bed06d7 Added two new key exchanges: 'kexAlgoCurve25519SHA256' and 'Curve25519SHA256'. 2020-07-28 21:17:29 -04:00
41e69dd6f2 Alphabetized options in usage message and README. 2020-07-16 12:07:02 -04:00
25faeb4c59 Added new man page. 2020-07-16 11:48:35 -04:00
8051078524 When a list of targets is provided (-T), skip empty lines. 2020-07-16 10:19:36 -04:00
cf815a6652 Added hardened OpenSSH policies. 2020-07-15 14:35:18 -04:00
2d4eb7da28 Renamed policies to include 'Hardened' in title. 2020-07-15 14:33:10 -04:00
68a420ff00 Added policy support for optional host key types, like certificates and smart card-based types. 2020-07-15 14:32:14 -04:00
17f5eb0b38 Added -L option to list built-in policies. 2020-07-14 19:38:10 -04:00
b95969bbc0 Policy output now more clearly prints the policy version. 2020-07-14 17:38:15 -04:00
00ce44e728 Added Ubuntu client policies. 2020-07-14 17:18:35 -04:00
8fb07edafd Added 'client policy' field in policy files to distinguish server from client policies. 2020-07-14 17:14:47 -04:00
b27d768c79 Print client IP in output when doing policy audits. 2020-07-14 14:01:08 -04:00
cb54c2bf33 Moved Windows build instructions to packages directory. 2020-07-14 11:03:35 -04:00
85f14720cb Added 3 new host keys: ssh-gost2001, ssh-gost2012-256, and ssh-gost2012-512. 2020-07-14 10:43:18 -04:00
1410894f45 Update description for targets argument (#48)
`targets` takes a file containing a list of target hosts, one on each
line.

Added required format, ie HOST:PORT.

modified:   ssh-audit.py
2020-07-14 10:35:54 -04:00
381ba1a660 Now supports a list of targets with -T (#11). 2020-07-13 18:39:05 -04:00
8e3f3c6044 Updated PyPI notes. 2020-07-11 12:42:11 -04:00
f80e3f22ce Now returns -1 when an uncaught exception is found. 2020-07-07 16:31:44 -04:00
49bd2c96a8 Added return values for standard scans. 2020-07-07 15:56:37 -04:00
103b8fb934 Added official policies for hardened Ubuntu 16.04, 18.04, and 20.04. 2020-07-06 16:16:52 -04:00
1faa24ad86 Do not accidentally overwrite policies when creating new policy with -M. 2020-07-06 16:15:26 -04:00
adc1007d7d Mark 'gss-group1-sha1-' kex as failure due to 1024-bit modulus. 2020-07-04 09:41:46 -04:00
8a406dd9d2 Simplify mypy config (#45)
Instead of specifying stricter checks one by one, just run `mypy` in
`strict` mode.

modified:   tox.ini
2020-07-04 09:39:43 -04:00
d717f86238 Added check for use-after-free vulnerability in PuTTY v0.73. 2020-07-03 15:07:34 -04:00
bf1fbbfa43 Fix RuntimeError for the JSON export (#44)
* Fix RuntimeError for the JSON export

It is never a good idea to modify an iterable while iterating over it.

Copying the iterable fixes #41

modified:   ssh-audit.py

* Add test case for #41

new file:   test/test_build_struct.py

* Fix linting error

modified:   test/test_build_struct.py
2020-07-03 14:56:46 -04:00
282770e698 Added 'ssh-dss-sha256@ssh.com' host key type, 'crypticore128@ssh.com' and 'seed-cbc@ssh.com' ciphers, and 'crypticore-mac@ssh.com' MAC. 2020-07-01 14:32:55 -04:00
01ec6b0b37 Removed header processing from policy checks, as this did not function the way users would expect. 2020-07-01 13:12:49 -04:00
30f2b7690a Enabled the following mypy options: check_untyped_defs, disallow_untyped_defs, disallow_untyped_calls, disallow_incomplete_defs, disallow_untyped_decorators, disallow_untyped_decorators, strict_equality, and strict. 2020-07-01 13:00:44 -04:00
cabbe717d3 Added 'diffie-hellman-group1-sha256' kex. 2020-06-30 22:58:28 -04:00
d5ef967758 Upgraded 1024-bit modulus warning to failure. 2020-06-30 22:51:13 -04:00
dd44e2f010 Added policy checks (#10). 2020-06-30 15:53:50 -04:00
8e71c2d66b Handle case of KexDH.recv_reply() returning None. 2020-06-27 23:59:15 -04:00
da31c19d38 Re-enable mypy options (#43)
* Convert type comments to annotations

Notes:
- variable annotations are only possible for Python 3.6 and upwards

- class names as a result of a function have to be quoted
cf https://www.python.org/dev/peps/pep-0563/#enabling-the-future-behavior-in-python-3-7

This is ongoing work for #32

modified:   ssh-audit.py

* Do not use variable annotation

... as this feature works only for Python 3.6 and above only.

modified:   ssh-audit.py

* Re-enable strict_optional

`None` is a valid return type for mypy, even when you specify a certain
type. `strict_optional` makes sure that only the annotated return type
is actually returned.

modified:   tox.ini

* Re-enable `warn_unused_ignores`

Quote from mypy docs:
This flag will make mypy report an error whenever your code uses a
`# type: ignore` comment on a line that is not actually generating
 an error message.

modified:   tox.ini

* Re-enable `warn_return_any`

Quote from the documenation:
"This flag causes mypy to generate a warning when returning a value with
type Any from a function declared with a non-Any return type."

modified:   tox.ini

* Re-enable `warn_redundant_casts`

Quote from the documentation:
"This flag will make mypy report an error whenever your code uses an
unnecessary cast that can safely be removed."

modified:   tox.ini

* Remove `warn_incomplete_stub`

... as the documentation says
"This flag is mainly intended to be used by people who want contribute
to typeshed and would like a convenient way to find gaps and omissions."

modified:   tox.ini

* Re-enable `disallow_subclassing_any`

Quote from the documentation:
"This flag reports an error whenever a class subclasses a value of type
Any."

modified:   tox.ini

* Re-enable `follow_imports`

... and set it to `normal`.

For more information, see
https://mypy.readthedocs.io/en/latest/running_mypy.html#follow-imports

modified:   tox.ini

* Re-enable `ignore_missing_imports`

Quote from the documentation:
"This flag makes mypy ignore all missing imports. It is equivalent to
adding # type: ignore comments to all unresolved imports within your
codebase."

modified:   tox.ini

* Fix arguments for Kex initialization

`follows` has to be a boolean, but an int was provided.

This worked, as in Python boolean is a subtype of int.

modified:   ssh-audit.py

* Do not uncomment `check_untyped_defs` yet

modified:   tox.ini

* Change KexDH.__ed25519_pubkey's default type

It was initialized with 0 (int), and later it gets set with bytes.

Now, it gets initialized with None, and thus gets the type
Optional[bytes].

Optional means None or the named type.

modified:   ssh-audit.py

* Fix whitespace

modified:   tox.ini

* Add type annotation for main function

modified:   ssh-audit.py

* Add type annotation for KexDH.set_params

modified:   ssh-audit.py

* Add type annotation for Kex.set_rsa_key_size

modified:   ssh-audit.py

* Add type annotation for Kex.rsa_key_sizes

modified:   ssh-audit.py

* Add type annotation for Kex.set_dh_modulus_size

modified:   ssh-audit.py

* Add type annotation to Kex.dh_modulus_sizes

modified:   ssh-audit.py

* Add type annotation for Kex.set_host_key

modified:   ssh-audit.py

* Add type annotation for Kex.host_keys

modified:   ssh-audit.py

* Add type annotation for HostKeyTest.run

modified:   ssh-audit.py

* Add static typing to HostKeyTest.perform_test

This revealed a small oversight in the guard protecting the call to
perform_test.

modified:   ssh-audit.py

* Add type annotation for GexTest.reconnect

modified:   ssh-audit.py

* Add type annotation for GexTest.run

modified:   ssh-audit.py

* Add type annotation for ReadBuf.reset

modified:   ssh-audit.py

* Add type annoation for WriteBuf.reset

modified:   ssh-audit.py

* Add type annotation to Socket.listen_and_accept

modified:   ssh-audit.py

* Move comment for is_connected into docstring.

modified:   ssh-audit.py

* Add type annotation for Socket.is_connected

modified:   ssh-audit.py

* Add type annotation for Socket.close

modified:   ssh-audit.py

* Do not commit breakpoint

modified:   ssh-audit.py

* Add annotations for KexDH key size handling

modified:   ssh-audit.py

* Add type annotation for KexDH.get_ca_size

modified:   ssh-audit.py

* Add type annotation to output_info

modified:   ssh-audit.py

* Add type annotation for KexDH.__get_bytes

modified:   ssh-audit.py

* Add type annotation to KexGroup14.__init__

modified:   ssh-audit.py

* Add type annotation for KexGroup14_SHA256.__init__

modified:   ssh-audit.py

* Add type annotation for KexGroup16_SHA512.__init__

modified:   ssh-audit.py

* Add type annotation for KexGroup18_SHA512.__init__

modified:   ssh-audit.py

* Add type annotation for KexCurve25519_SHA256.__init__

modified:   ssh-audit.py

* Add type annotation for KexNISTP256.__init__

modified:   ssh-audit.py

* Add type annotations to several init methods

modified:   ssh-audit.py

* Add type annotataion for KexGroupExchange.send_init_gex

modified:   ssh-audit.py

* Add type annotation for KexGroupExchange.__init__

modified:   ssh-audit.py

* Add type annotation to KexCurve25519_SHA256.send_init

modified:   ssh-audit.py

* Add type annotation for KexNISTP256.sent_init

modified:   ssh-audit.py

* Add type annotation for KexNISTP384.send_init

modified:   ssh-audit.py

* Add type annotation for KexNISTP521.send_init

modified:   ssh-audit.py

* Add type annotation for KexGroupExchange.send_init

modified:   ssh-audit.py

* Add type annotation to KexDH.get_dh_modulus_size

modified:   ssh-audit.py

* Delete unused variables KexDH.__f and f_len

__f was initialized as int, then assigned to bytes, but never used.

f_len assigned an int, but not all.

modified:   ssh-audit.py

* Delete unused variables KexDH.__h_sig and h_sig_len

modified:   ssh-audit.py

* Add type annotation for KexDH.__hostkey_type

modified:   ssh-audit.py
2020-06-27 23:54:34 -04:00
a75be9ab41 Convert type comments to annotations (#40)
* Convert type comments to annotations

Notes:
- variable annotations are only possible for Python 3.6 and upwards

- class names as a result of a function have to be quoted
cf https://www.python.org/dev/peps/pep-0563/#enabling-the-future-behavior-in-python-3-7

This is ongoing work for #32

modified:   ssh-audit.py

* Do not use variable annotation

... as this feature works only for Python 3.6 and above only.

modified:   ssh-audit.py
2020-06-26 17:51:08 -04:00
d168524a5d Updated README. 2020-06-16 23:10:08 -04:00
1f48e7c92b Fix typing errors (#39)
* Remove obsolete option `strict_boolean`

It was removed back in 2018:
https://github.com/python/mypy/pull/5740/files

modified:   tox.ini

* Deactivate all mypy options

This still leaves 47 errors, which should be fixed one by one.

modified:   tox.ini

* Fix signature for `output`

`client_host` is either a str or None.

modified:   ssh-audit.py

* Fix return value for `output_recommendations`

It is a bool, not None.

modified:   ssh-audit.py

* Fix type comment for `output_fingerprints`

modified:   ssh-audit.py

* Fix type comment for `output_security`

modified:   ssh-audit.py

* Fix type comment for `output_security_sub`

modified:   ssh-audit.py

* Fix type comment for `output_compatibility`

modified:   ssh-audit.py

* Fix type comment for Socket.__init__

modified:   ssh-audit.py

* Simplify check for regex result

... which also fixes four typing errors.

modified:   ssh-audit.py

* Fix typing error by simplifying regex result check

modified:   ssh-audit.py

* Fix typing errors by simplifying regex result check

modified:   ssh-audit.py

* Fix typing errors by simplifying regex result check

modified:   ssh-audit.py

* Fix typing errors by simplifying regex result check

modified:   ssh-audit.py

* Fix typing errors by simplifying regex result check

modified:   ssh-audit.py

* Fix typing errors by simplifying regex result check

modified:   ssh-audit.py

* Fix typing error by simplifying regex result check

modified:   ssh-audit.py

* Fix typing error by simplifying regex result check

modified:   ssh-audit.py

* Fix typing error by simplifying regex result check

modified:   ssh-audit.py

* Fix typing error by simplifying regex result check

modified:   ssh-audit.py

* Fix typing errors by simplifying regex result check

modified:   ssh-audit.py

* Fix typing errors by simplifying regex result check

modified:   ssh-audit.py

* Fix typing error by simplifying regex result check

modified:   ssh-audit.py

* Fix typing error by simplifying regex result check

modified:   ssh-audit.py

* Fix typing error by simplifying regex result check

modified:   ssh-audit.py

* Fix typing error by simplifying regex result check

modified:   ssh-audit.py

* Fix type comment for OutputBuffer.flush

modified:   ssh-audit.py

* Fix type comments for `output_algorithms`

modified:   ssh-audit.py

* Fix type comment for `output_algorithm`

modified:   ssh-audit.py

* Fix type comment for KexGroup14's init method

modified:   ssh-audit.py

* Fix type comment for KexDH's send_init

modified:   ssh-audit.py

* Fix type comment for KexDH's init method

modified:   ssh-audit.py

* Add explicit return value of None

Now this is odd. Python has an implicit return value of None for
`return`, but mypy does not infer that.

modified:   ssh-audit.py

* Fix type error for unknown_algorithms

modified:   ssh-audit.py

* Add type comment for Socket.__sock_map

modified:   ssh-audit.py

* Create type comment for Kex.__host_keys

modified:   ssh-audit.py

* Create type comment for Kex.__dh_modulus_sizes

modified:   ssh-audit.py

* Add type comment for Kex.__rsa_key_sizes

modified:   ssh-audit.py

* Fix type eror by adding type comment to temp variable

modified:   ssh-audit.py

* Fix type comments for Auditconf.__setattr__

modified:   ssh-audit.py

* Fix type error by simplifying branch logic

modified:   ssh-audit.py

* Do not skip type checks any more

Without additional strict options, there are zero type errors (down from
47).

modified:   tox.ini

* Annotate variables before tuple unpacking

modified:   ssh-audit.py

* Annotate variables before unpacking

modified:   ssh-audit.py

* Fix flake8 issues

modified:   ssh-audit.py
2020-06-16 22:54:39 -04:00
12f811cb5c Remove native text converter (#38)
* Remove `native text` converter

This was only necessary with Python 2. After Python 2 removal, both
functions `to_ntext` and `to_utext` exactly did the same.

modified:   ssh-audit.py
modified:   test/test_utils.py

* Rename `to_utext` to `to_text`

... as in Python 3 there is only text (and bytes).

modified:   ssh-audit.py
modified:   test/test_utils.py
2020-06-16 22:50:07 -04:00
ec1dda8d7f Remove some more Python 2 leftovers (#37)
* Remove mypy job for Python 2

modified:   tox.ini

* Remove Python 2 compatibility import

modified:   ssh-audit.py

* Remove compatibility import for BytesIO and StringIO

This is no longer necessary, as support for Python 2 was dropped.

modified:   ssh-audit.py

* Remove `text-type` compatibility layer

... as support for Python 2 was dropped already.

modified:   ssh-audit.py

* Remove `binary-type` compatibility layer

... as support for Python 2 was dropped already.

modified:   ssh-audit.py

* Remove try-except block for typing

... as since Python 3.5 it is included in the standard library.

modified:   ssh-audit.py

* Move typing import to top of module

modified:   ssh-audit.py

* Remove obsolete encoding declaration

modified:   ssh-audit.py

* Apply pyupgrade on ssh-audit.py

pyupgrade is a tool which updates Python code to modern syntax

modified:   ssh-audit.py

* Remove Python 2 compatibility from conftest.py

modified:   test/conftest.py

* Remove Python 2 compatibility from test_auditconf.py

modified:   test/test_auditconf.py

* Remove Python 2 compatibility from test_banner.py

modified:   test/test_banner.py

* Remove Python 2 compatibility from test_buffer.py

modified:   test/test_buffer.py

* Remove Python 2 compatibility from test_errors.py

modified:   test/test_errors.py

* Remove Python 2 compatibility from test_output.py

modified:   test/test_output.py

* Remove Python 2 compatibility from test_resolve.py

modified:   test/test_resolve.py

* Remove Python 2 compatibility from test_socket.py

modified:   test/test_socket.py

* Remove Python 2 compatibility from test_software.py

modified:   test/test_software.py

* Remove Python 2 compatibility from test_ssh_algorithm.py

modified:   test/test_ssh_algorithm.py

* Remove Python 2 compatibility from test_ssh1.py

modified:   test/test_ssh1.py

* Remove Python 2 compatibility from test_ssh2.py

modified:   test/test_ssh2.py

* Remove Python 2 compatibility and Py2 only tests

... from test_utils.py.

modified:   test/test_utils.py

* Remove Python 2 compatibility from test_version_compare.py

modified:   test/test_version_compare.py

* Remove Python 2 job from appveyor config

This was done blindly, as it is unclear whether appveyor runs at all.

modified:   .appveyor.yml
2020-06-15 17:05:31 -04:00
42fecf83e6 Re-enabled test_ssh2_server_simple. Fixes #33. 2020-06-13 12:22:59 -04:00
9463aab4f7 Disable Python2 tests. Fix pylint warnings. 2020-06-13 11:27:01 -04:00
22ac41bfb8 Converted tab indents to spaces. 2020-06-12 21:01:10 -04:00
246a41d46f Flake8 fixes (#35)
* Apply Flake8 also on `setup.py`

modified:   tox.ini

* Fix W605 - invalid escape syntax

modified:   packages/setup.py
modified:   tox.ini

* Update comment about Flake8: W504

W503 and W504 are mutual exclusive - so we have to keep one of them.

modified:   tox.ini

* Fix F841 - variable assigned but never used

modified:   ssh-audit.py
modified:   tox.ini

* Fix E741 - ambiguous variable name 'l'

modified:   ssh-audit.py
modified:   tox.ini

* Fix E712 - comparison to False should be 'if cond is False'

... and not 'if conf == False'.

modified:   ssh-audit.py
modified:   tox.ini

* Fix E711 - comparison to None should be 'if cond is not None'

... and not 'if cond != None'.

modified:   ssh-audit.py
modified:   tox.ini

* Fix E305 - expected 2 blank lines

... after class or function definition, found 1.

modified:   ssh-audit.py
modified:   tox.ini

* Fix E303 - too many blank lines

modified:   ssh-audit.py
modified:   tox.ini

* Fix E303 - too many blank lines

modified:   ssh-audit.py
modified:   tox.ini

* Fix E301 - expected 1 blank line, found 0

No code change necessary, probably fixed by another commit.

modified:   tox.ini

* Fix E265 - block comment should start with '# '

There is lots of commented out code, which usually should be just
deleted.

I will keep it for now, as I am not yet very familiar with the code
base.

modified:   ssh-audit.py
modified:   tox.ini

* Fix E261 - at least two spaces before inline comment

modified:   ssh-audit.py
modified:   tox.ini

* Fix E251 - unexpected spaces around keyword / parameter equals

modified:   packages/setup.py
modified:   tox.ini

* Fix E231 - missing whitespace after ','

No code change necessary, probably fixed by previous commit.

modified:   tox.ini

* Fix E226 - missing whitespace around arithmetic operator

modified:   ssh-audit.py
modified:   tox.ini

* Fix W293 - blank line contains whitespace

modified:   ssh-audit.py
modified:   tox.ini

* Fix E221 - multiple spaces before operator

modified:   ssh-audit.py
modified:   tox.ini

* Update comment about Flake 8 E241

Lots of data is formatted as tables, so this warning is disabled for a
good reason.

modified:   tox.ini

* Fix E401 - multiple imports on one line

modified:   ssh-audit.py
modified:   tox.ini

* Do not ignore Flake8 warning F401

... as there were no errors in source code anyway.

modified:   tox.ini

* Fix F821 - undefined name

modified:   ssh-audit.py
modified:   tox.ini

* Reformat ignore section for Flake8

modified:   tox.ini

* Flake8 test suite

modified:   test/conftest.py
modified:   test/test_auditconf.py
modified:   test/test_banner.py
modified:   test/test_buffer.py
modified:   test/test_errors.py
modified:   test/test_output.py
modified:   test/test_resolve.py
modified:   test/test_socket.py
modified:   test/test_software.py
modified:   test/test_ssh1.py
modified:   test/test_ssh2.py
modified:   test/test_ssh_algorithm.py
modified:   test/test_utils.py
modified:   test/test_version_compare.py
modified:   tox.ini
2020-06-09 17:54:07 -04:00
29d874b450 Fix tox and finally make Travis green (#29)
* Ignore all flake8 warnings - one by one

Without ignoring, there are by far more than 1000 linting issues.

Fixing these warnings means possibly changing almost every line of
code, as single warnings can effect more than one line.

Doing this in one pull request is generally no good idea, and especially
not now, as the test suite is currently broken.

Instead of just deactivating flake8, or ignoring its exit code, the
warnings are ignored one by one.

This means, when one wants to work on the linting issues, one can just
remove one ignored warning, and fix the problems - which is not too much
work at once, and leads to an managable diff.

modified:   tox.ini

* Unpin dependencies for mypy run

... as they could not be installed due to compilation errors.

modified:   tox.ini

* Fix syntax error for mypy

When new code was added via
af663da838
the type hint was moved further down and so caused a syntax error, as
type hints have to follow the function declaration directly.

Now, the the type linter finally works and shows 187 errors.

modified:   ssh-audit.py

* Update .gitignore for mypy

modified:   .gitignore

* Let tox not fail on mypy errors

Currently, there are almost 200 typing related errors.

Instead of letting the tox run fail, the errors are still shown, but
the exit code gets ignored for now.

This way one can fix them one by one - if wanted.

modified:   tox.ini

* Let tox not fail on pylint errors

Currently, there are more than 100 linting related errors.

Most of them will be fixed when flake8 gets fixed.

Instead of letting the tox run fail, the errors are still shown, but the
exit code gets ignored for now.

This way, one can fix them one by one.

modified:   tox.ini

* Let vulture only fail on 100% confidence

Vulture is a tool to find dead code. Unlike Flake8, which also finds
unused imports and variables, Vulture does some guess work and finally
outputs a list of possible dead code with a confidence marker.

Already the first result ...
"ssh-audit.py:48: unused import 'Dict' (90% confidence)"
... is a false-positive.

As Flake8 also does a good job in detecting unused code, it makes not
much sense to let tox fail when vulture fails.

Instead of deactivating vulture, it was configured in a way to only
report results with 100% confidence.

modified:   tox.ini

* Make timeout_set optional

When timeout_set was introduced in
1ec13c653e
the tests were not updated, which instantiated the Socket class.

While the commit message read "A timeout can now be specified", the
code enforced a `timeout_set`.

`timeout_set` now is `False` by default.

modified:   ssh-audit.py

* Set default values for Socket's `ipvo` and `timeout`

Commit
f44663bfc4
introduced two new arguments to the Socket class, but did not update
the tests, which still relied on the socket class to only require two arguments.

While for `ipvo`the default of `None` is obvious, as in `__init__` it is
checked for it, for `timeout` it was not that obvious.

Luckily, in the README a default of 5 (seconds) is mentioned.

modified:   ssh-audit.py

* Un-comment exception handling

While working on commit
fd3a1f7d41
possibly it was forgotten to undo the commenting of the exception
handling for the case, when the Socket class was instantiated with a
missing `host` argument.

This broke the `test_invalid_host` test.

modified:   ssh-audit.py

* Skip `test_ssh2_server_simple` temporarily

After fixing all the other tests and make tox run again, there is one
failing test left, which unfortunately is not super easy to fix without
further research (at least not for me).

I marked `test_ssh2_server_simple` to be skipped in test runs
(temporarily), so at least, when working on new features, there is
working test suite, now.

modified:   test/test_ssh2.py

* Do not pin pytest and coverage version

... but do use pytest < 6, as this version will have a breaking change
with junit/Jenkins integration

Also see https://github.com/jtesta/ssh-audit/issues/34

* Drop unsupported Python versions

... except Python 2.7, as this will need also changes to the source
code, and this pull request is already big enough.

Also, support for Python 3.8 was added.

The Travis configuration was simplified a lot, by leveraging the tox
configuration.

Also, the mac builds have been dropped, as they all took almost an hour
each, they failed and I have no experience on how to fix them.

The `appveyor` build only has been updated to reflect the updated Python
versions, as I have no access to the status page and no experience with
this build environment.

Also, removed call to `coveralls`, which seems to be a leftover from
the old repository.

modified:   .appveyor.yml
modified:   .travis.yml
modified:   packages/setup.py
deleted:    test/tools/ci-linux.sh
modified:   tox.ini
2020-06-08 16:38:22 -04:00
bbc4ab542d Added Homebrew installation instructions (#27). 2020-05-31 11:44:00 -04:00
edc363db60 Suppress recommendation of token host key types. 2020-05-31 11:42:06 -04:00
4b314a55ef Added 2 new ciphers: AEAD_AES_128_GCM and AEAD_AES_256_GCM. 2020-03-24 14:12:15 -04:00
4ffae85325 Added hmac-sha3-224 MAC. 2020-03-20 09:16:41 -04:00
2c4fb971cd Added 1 new MAC: chacha20-poly1305@openssh.com. 2020-03-20 00:34:04 -04:00
1ac4041c09 Added one new host key type (ssh-rsa1) and one new cipher (blowfish). 2020-03-18 12:19:05 -04:00
b70f4061cc Added PyPI and snap package info. 2020-03-12 23:20:31 -04:00
c3aaf6e2a7 Added snap package support. 2020-03-12 21:56:23 -04:00
f35c7dbee7 Updated PyPI notes. 2020-03-11 12:45:28 -04:00
e447c42a79 Bumped version to v2.2.0. 2020-03-11 11:55:14 -04:00
5292066e66 Added new ciphers (camellia128-cbc, camellia128-ctr, camellia192-cbc, camellia192-ctr, camellia256-cbc, camellia256-ctr). Fixed certain algorithms not appearing in the recommendations list (#16). 2020-03-10 19:22:15 -04:00
c043570879 Merge pull request #20 from KiloFoxtrotPapa/fix-lopt-port
Fix long option for port=
2020-02-27 10:48:51 -05:00
kfp
a04c96c5b2 Fix long option for port= 2020-02-21 22:21:54 -08:00
c9a2f2955c Marked host key type 'ssh-rsa' as weak due to practical SHA-1 collisions. 2020-02-08 23:56:54 -05:00
99ae10440b Added new hostkey types for OpenSSH 8.2. 2020-02-08 19:05:36 -05:00
8cafcd4eb5 Added many new algorithms. 2020-02-08 18:44:42 -05:00
262e9b1826 Added *.exe and *.asc to ignore list. 2019-12-25 14:42:17 -05:00
06f868d76f Added timeout of 0 to container stop command. 2019-11-30 23:49:31 -05:00
8e3e8aa423 Updated README regarding Windows builds. 2019-11-30 17:15:54 -05:00
96b6a62f05 Added Windows build instructions and icon. 2019-11-30 16:55:43 -05:00
229a4f2af9 Bumped version number. 2019-11-26 12:13:56 -05:00
5c63f907f7 Updated pypi package description. 2019-11-26 12:13:07 -05:00
cba89f70e3 Updated pypi notes. 2019-11-26 12:12:47 -05:00
dc36622b50 Bumped version to v2.1.1. 2019-11-26 11:48:18 -05:00
8e0b83176a Updated ChangeLog. Added link to hardening guide. 2019-11-26 11:47:35 -05:00
a16eb2d6cb Added three new PuTTY vulns. 2019-11-18 22:08:17 -05:00
2848c1fb16 Added two new ciphers: 'des', and '3des'. 2019-11-18 20:22:12 -05:00
2cff202b32 Added two new host key types: 'rsa-sha2-256-cert-v01@openssh.com' and 'rsa-sha2-512-cert-v01@openssh.com'. 2019-11-14 16:45:40 -05:00
dae92513fd During client tests, client IP is now listed in output. 2019-11-14 13:52:36 -05:00
e101e22720 Bumped version number. 2019-11-14 11:07:16 -05:00
b3a46e8318 Added pypi notes. 2019-11-14 11:06:05 -05:00
3606863ebf Updated client auditing screen shot. 2019-11-14 10:15:30 -05:00
cf1e069db4 Updated version to 2.1.0. 2019-11-14 08:56:43 -05:00
1e1220807f Updated README with client auditing screen shot and credit for JSON output code. 2019-11-14 08:56:14 -05:00
0263769243 Added JSON output tests to docker testing suite. 2019-11-08 18:40:32 -05:00
e6c31ee4f5 Added JSON output to ChangeLog. 2019-11-08 11:10:41 -05:00
14e0ed0e00 Fixed minor whitespace issue. 2019-11-08 11:09:00 -05:00
0f21f2131c Merge pull request #12 from x-way/json_output
RFC: JSON output
2019-11-08 11:06:09 -05:00
b6c64d296b Add --json output option 2019-11-07 22:08:09 +01:00
1ec13c653e A timeout can now be specified when auditing client connections. 2019-11-06 20:40:25 -05:00
a401afd099 Merged arthepsy/ssh-audit#47 2019-10-25 11:48:02 -04:00
8a3ae321f1 Added five kex algorithms: gss-gex-sha1-toWM5Slw5Ew8Mqkay+al2g==, gss-group14-sha1-, gss-group14-sha1-toWM5Slw5Ew8Mqkay+al2g==, gss-group14-sha256-toWM5Slw5Ew8Mqkay+al2g==, gss-group15-sha512-toWM5Slw5Ew8Mqkay+al2g==; added four ciphers: idea-cbc, serpent128-cbc, serpent192-cbc, serpent256-cbc; added four MACs: hmac-ripemd, hmac-sha256-96@ssh.com, umac-32@openssh.com, umac-96@openssh.com. 2019-10-25 11:27:22 -04:00
e62b548677 Updated info on curve25519-sha256 kex. 2019-10-21 11:50:23 -04:00
fd85e247e7 Improved IPv4/IPv6 error handling during client testing. 2019-10-10 23:09:45 -04:00
e3a59a3e21 Client auditing feature now supports IPv6. 2019-10-09 22:29:56 -04:00
4ebefdf894 Updated usage message. 2019-10-09 21:12:09 -04:00
83544836c9 Fixed client parsing crash. 2019-10-09 20:57:31 -04:00
4c9b871f5c Removed duplicate MAC. 2019-10-07 11:00:11 -04:00
1d707276d7 Updated README. 2019-10-07 10:59:52 -04:00
166c93ace4 Updated project description to mention client auditing ability. 2019-09-27 18:19:49 -04:00
9759480ae4 Updated ChangeLog. 2019-09-27 18:16:50 -04:00
fd3a1f7d41 Added client audit functionality. (#3) 2019-09-27 18:14:36 -04:00
08677d65b1 Added potential fix for additional crash against Sun_SSH. 2019-09-19 22:25:30 -04:00
8c5493ae3e Added 2 key exchanges (ecdh-sha2-1.3.132.0.10, curve448-sha512), 1 host key type (ecdsa-sha2-1.3.132.0.10), and 2 MACs (hmac-sha2-256-96-etm@openssh.com, hmac-sha2-512-96-etm@openssh.com). 2019-09-19 22:19:26 -04:00
14af53cf04 Updated ChangeLog for v2.1.0. 2019-09-19 20:10:37 -04:00
bbf6204ce1 Add support for Sun_SSH (on Solaris). Add 'gss-group1-sha1-toWM5Slw5Ew8Mqkay+al2g==' key exchange. 2019-09-19 20:08:10 -04:00
0df63c20ac Updated screen shot. 2019-09-05 18:52:32 -04:00
209bcab427 Added entries to .gitignore. 2019-09-04 15:06:49 -04:00
eac81455a9 Added PyPI package support. 2019-09-04 15:05:07 -04:00
bce9e2b152 Added new KEX: diffie-hellman-group15-sha256. 2019-09-03 20:41:53 -04:00
f5431559ff Bumped version number. 2019-08-29 16:52:38 -04:00
6f60722455 Fixed version number. 2019-08-29 15:53:35 -04:00
f7cbe71aba Updated for v2.0.0 release. 2019-08-29 15:34:19 -04:00
c185a25af1 For unrecognized servers, only recommend algorithm changes & removals, not additions (since they can be very inaccurate). 2019-08-28 00:37:55 -04:00
7221413567 Added TinySSH test. 2019-08-27 22:28:24 -04:00
747177c1c7 Added TinySSH support. Fixes #7. 2019-08-27 17:02:03 -04:00
6846b1bf29 Added two KEX algorithms: diffie-hellman-group16-sha256 and diffie-hellman-group-exchange-sha512@ssh.com. 2019-08-26 15:28:37 -04:00
af7e2a088c Added hmac-sha512 and hmac-sha512@ssh.com MACs. Added diffie-hellman-group17-sha512 key exchange. 2019-08-26 15:19:49 -04:00
120f898539 Added Dropbear test. 2019-08-26 14:45:31 -04:00
0b034b8226 Marked 3des-ctr as a weak cipher. 2019-08-26 14:44:35 -04:00
4ebccb8068 Added OpenSSH v4.0 test. 2019-08-22 16:48:23 -04:00
4f138d7f82 Added docker testing framework. 2019-08-22 16:04:46 -04:00
7a06b872f9 Fixed automatic links in changelog. 2019-08-22 15:54:14 -04:00
6baff0f8fe Updated changelog for v2.0.0. 2019-08-22 15:49:10 -04:00
af663da838 Now SHA256 fingerprints are displayed for RSA and ED25519 host keys. Fixes #2. 2019-08-22 15:47:37 -04:00
ed11fc135b When unknown algorithms are encountered, ask the user to report them. 2019-08-18 15:20:16 -04:00
afa73d2dd2 Added 1 kex (diffie-hellman-group-exchange-sha256@ssh.com), 3 encryption algs (des-cbc-ssh1, blowfish-ctr, twofish-ctr), and 8 macs (hmac-sha2-56, hmac-sha2-224, hmac-sha2-384, hmac-sha3-256, hmac-sha3-384, hmac-sha3-512, hmac-sha256, hmac-sha256@ssh.com). 2019-08-18 14:38:39 -04:00
64656b5228 Added timeout option to usage message. 2019-08-18 10:03:44 -04:00
99ac875542 Added timeout argument. 2019-08-18 10:03:03 -04:00
f9a51d4108 Default interpreter changed to python3. 2019-08-18 00:34:03 -04:00
8527d13343 Added documentation on ALGORITHMS structure. 2019-08-18 00:32:59 -04:00
f8fcd119e2 Tagged sntrup4591761x25519-sha512@tinyssh.org as experimental, just as the OpenSSH 8.0 release notes say. 2019-08-18 00:16:42 -04:00
76a4750934 Added support for kex sntrup4591761x25519-sha512@tinyssh.org, introduced in OpenSSH 8.0. 2019-08-18 00:09:40 -04:00
7155efeb4a Added CVEs for Dropbear & libssh. Fixed libssh CVE parsing. Now prints CVEs in red when score is >= 8.0, otherwise they are printed in orange. 2019-08-17 23:11:03 -04:00
41d396f551 Updated version, copyright header, URL, and added Python 2 warning. 2019-08-17 20:59:23 -04:00
a9933f9211 Added myself to copyright header in license. 2019-08-16 08:56:50 -04:00
b35ca6c6f3 Merged all_my_patches branch to master, since a new project maintainer is needed. 2019-08-16 08:30:45 -04:00
f2e6f1a71c Replace getopt.getopt with getopt.gnu_getopt
Addresses Issue #41, gnu_getopt allows non-option arguments to be intermingled with option arguments whereas getopt stops processing arguments when a non option is found.
2019-06-05 16:19:33 -04:00
f44663bfc4 Fixed Socket.connect() method arguments. 2017-10-31 16:49:19 -04:00
95ca0bb243 Fixed merge collision in connect() method. 2017-10-31 16:40:02 -04:00
a9f6b93391 Merge branch 'timeout_arg' into all_my_patches 2017-10-31 16:36:20 -04:00
04973df2af Added command-line option to modify connection/read timeout. 2017-10-29 17:48:04 -04:00
a3f126a1dd Added missing algorithms from RFC4250 and RFC4432. 2017-10-11 15:47:01 -04:00
1bb5490e01 Added new algorithms (some as per RFC4344). 2017-10-11 15:13:58 -04:00
c1d0540d1e Fixed one more warning. 2017-09-27 22:42:49 -04:00
cd80917c62 Fixed more warnings. 2017-09-27 22:36:23 -04:00
b7bf8ab38a Suppressed more unused variables warnings. 2017-09-27 22:22:42 -04:00
a3c6d16500 Suppressing pylint warnings on unused variables. 2017-09-27 22:14:48 -04:00
4f6e23e568 Fixed send_init() inheritance problems. Now kex failures will try to continue on instead of terminating the program. 2017-09-27 21:27:08 -04:00
b2775c9cf9 Python3 fixes. 2017-09-26 20:51:10 -04:00
ee5dde1cde Added RSA certificate auditing. 2017-09-26 20:46:00 -04:00
33ae2946ea Syntax fix for Python2. 2017-09-22 15:01:51 -04:00
7c919b093b Added RSA & DH modulus size auditing. 2017-09-21 22:44:34 -04:00
d8eb46d766 Correct IPv6 parsing in command-line. Fixes #26. 2017-05-05 14:12:45 +03:00
96d442ec62 Test Timeframe repr(). 2017-04-11 13:32:38 +03:00
9c463b4e06 Fix lint tox environment. 2017-04-10 19:32:40 +03:00
1d1f842bed Refactor output level/colors, fix python:S1845. 2017-04-10 19:20:31 +03:00
72a6b9eeaf Refactor and test SSH.Algorithm. 2017-04-10 13:20:32 +03:00
774d1c1fe4 Ignore linting long assertion lines. 2017-04-10 13:20:02 +03:00
6c8173d409 Fix to_ntext test. 2017-04-06 05:27:40 +03:00
21a93cbd66 Condition must be a boolean fixes. 2017-04-06 05:27:29 +03:00
0d555d43b3 Condition must be a boolean fixes. 2017-04-05 18:12:26 +03:00
e4bdabb891 Fix method type and naming. 2017-04-05 17:34:19 +03:00
c132c62b96 Remove useless parentheses. 2017-04-05 16:13:35 +03:00
bb122ffe13 Replace assertions with exceptions. 2017-04-05 16:02:40 +03:00
09c2e7b2d5 Fix SonarQube python:S1871. 2017-04-05 04:27:39 +03:00
464bb154f3 Use git commit as dev version suffix. Add badge. 2017-04-05 04:25:01 +03:00
9fe69841eb Integrate SonarQube analysis. 2017-04-05 03:22:13 +03:00
f330608278 Test with pypy and pypy3 environments. 2017-04-01 10:21:50 +03:00
cab83f837a Update to Xcode 8.3. 2017-03-31 02:48:51 +03:00
041805f608 Test with AppVeyor environment. 2017-03-30 16:31:12 +03:00
2f7c64d896 Report python version in CI. 2017-03-28 10:25:55 +03:00
e91bbb5e30 Better testing environment. 2017-03-28 07:49:52 +03:00
95ba7d11ce Test on Ubuntu 12.04/14.04 and Mac OS X 10.10-10.12. 2017-03-26 17:47:13 +03:00
0ffb15dd54 Pylint and flake8 is not supported on Python 2.6. 2017-03-26 06:48:44 +03:00
76849540be It's 2017 already. 2017-03-26 06:31:06 +03:00
57a8744d03 Fix some unused variable warnings. 2017-03-26 06:24:07 +03:00
3ebb59108b Ignore pylint's else-if-used in validly used places. 2017-03-26 05:58:39 +03:00
74d1b5c7b5 Fix pylint's bad-builtin and deprecated-lambda with list comprehension. 2017-03-26 05:54:14 +03:00
6d9f5e6f2a Refactor tox.ini to be more versatile. 2017-03-26 05:42:20 +03:00
29d9e4270d Fix flake8 reported issues. 2017-03-25 08:44:37 +02:00
cfae0d020a Fix vulture output for Python 3. 2017-03-25 08:33:16 +02:00
8b7659c4d3 Remove unnecessary files, now that everything is in tox. Add codecov badge. 2017-03-25 08:02:49 +02:00
d3ba5a4e6f Use tox, use codecov, work around pypy3 issues. 2017-03-25 07:44:27 +02:00
65ef250aae Upgrade to Mypy 0.501 and fix issues. Add requirements.txt. 2017-03-23 23:17:35 +02:00
94a74e9cfd Reviewed libssh-0.7.4 changes. 2017-02-13 13:33:50 +02:00
9ac03d368a Add OpenSSH 7.4 changes and use as default banner. 2017-01-24 12:45:53 +02:00
54b0960502 Upgrade to Mypy 0.470. Add colorama stub. Fix identation. 2017-01-23 19:34:06 +02:00
c9443e6e06 Fix pyp3 version for Travis-CI (https://github.com/travis-ci/travis-ci/issues/6277). 2017-01-23 19:20:42 +02:00
bs
ff500ba84b Add OpenSSH CVE list (#25) 2017-01-23 17:45:25 +02:00
9a409e835e Refactor outer functions within classes.
Use mypy strict optional checks and fix them.
Use better comparison for compatiblity output.
Add initial socket tests.
2016-11-03 19:10:49 +02:00
6fde896d77 Add resolve tests. 2016-11-02 19:29:21 +02:00
6c4b9fcadf Banner should be in printable ASCII, not the whole ASCII space. 2016-11-02 18:25:13 +02:00
5bb0ae0ceb Rework is/to ASCII and implement printable ASCII is/to functions.
Add Utils tests.
2016-11-02 18:23:55 +02:00
11b6155c64 Use Python defined error numbers. 2016-11-02 13:18:03 +02:00
b3ed4c7715 Add LICENSE file (#22)
Create LICENSE
2016-11-02 13:03:30 +02:00
44c1d4827c Specify error when couldn't get banner. Test for timeout and retry cases. 2016-11-02 13:00:24 +02:00
22b671e15f Add LICENSE file (#22)
Create LICENSE
2016-11-02 12:45:56 +02:00
dd3ca9688e Back to development version. 2016-10-26 19:14:03 +03:00
e42064b9b9 Release 1.7.0. 2016-10-26 19:02:13 +03:00
8c24fc01e8 Merge branch 'develop' 2016-10-26 19:00:44 +03:00
4fbd339c54 Document changes and add coverage badge. 2016-10-26 18:56:38 +03:00
66b9e079a8 Implement new options (-4/--ipv4, -6/--ipv6, -p/--port <port>).
By default both IPv4 and IPv6 is supported and order of precedence depends on OS.
By using -46, IPv4 is prefered, but by using -64, IPv6 is preferd.
For now the old way how to specify port (host:port) has been kept intact.
2016-10-26 18:33:00 +03:00
8018209dd1 Fixed typos 2016-10-26 12:17:31 +03:00
7314d780e7 Merge pull request #20 from radarhere/master
Fixed typo
2016-10-26 12:11:04 +03:00
6a1f5d2d75 Fixed typos 2016-10-26 05:52:58 +11:00
4684ff0113 Add linter fixes for tests. 2016-10-25 17:19:08 +03:00
84dfdcaf5e Invalid CRC32 checksum test. 2016-10-25 16:59:43 +03:00
318aab79bc Add simple server tests for SSH1 and SSH2. 2016-10-25 16:57:30 +03:00
aa4eabda66 Do not count coverage for missing import. 2016-10-25 14:04:54 +03:00
4bbb1f4d11 Use safer UTF-8 decoding (with replace) and add related tests. 2016-10-25 13:53:51 +03:00
66bd6c3ef0 Test colors only if they are supported. 2016-10-25 11:57:13 +03:00
182467e0e8 Fix typo, which slipped in while adding type system. 2016-10-25 11:52:55 +03:00
385c230376 Add colors support for Microsoft Windows via optional colorama dependency. 2016-10-25 11:50:12 +03:00
855d64f5b1 Ignore virtualenv and cache. 2016-10-25 03:13:42 +03:00
5b3b630623 Fix pylint reported issues and disable unnecessary ones. 2016-10-20 20:00:51 +03:00
a5f1cd9197 Tune prospector and pylint settings. 2016-10-20 20:00:29 +03:00
cdfe06e75d Fix type after argument removal. 2016-10-20 17:19:37 +03:00
cbe7ad4ac3 Fix pylint reported no-self-use and disable checks in py2/3 compatibility code. 2016-10-20 17:06:23 +03:00
dfb8c302bf Fix pylint reported attribute-defined-outside-init. 2016-10-20 16:46:53 +03:00
4120377c0b Remove unnecessary argument. 2016-10-20 16:41:44 +03:00
5be64a8ad2 Fix pylint reported dangerous-default-value. 2016-10-20 16:31:48 +03:00
67087fb920 Fix pylint reported anomalous-backslash-in-string. 2016-10-20 16:27:11 +03:00
42be99a2c7 Test for non-ASCII banner. 2016-10-19 20:53:47 +03:00
ca6cfb81a2 Import mypy configuration script and run scripts (for Python 2.7 and 3.5).
Import pytest coverage script.
2016-10-19 20:51:57 +03:00
fabb4b5bb2 Add static typing and refactor code to pass all mypy checks.
Move Python compatibility types to first lines of code.
Add Python (text/byte) compatibility helper functions.
Check for SSH banner ASCII validity.
2016-10-19 20:47:13 +03:00
8ca6ec591d Handle the case when received data is in wrong encoding (not utf-8). 2016-10-18 09:45:03 +03:00
6b76e68d0d Fix wrongly introduced Python 3 incompatibility. Fixes #14 and #15.
Add static type checks via mypy (optional static type checker),
Add relevant tests, which could trigger the issue.
2016-10-17 20:31:13 +03:00
f065118959 Create virtual socket fixture (socket mocking). 2016-10-17 20:27:35 +03:00
63a9c479a7 Test kex payload generation. 2016-10-14 16:17:38 +03:00
c9d58bb827 Switch to new development version. 2016-10-14 09:14:07 +03:00
175 changed files with 11543 additions and 2819 deletions

33
.appveyor.yml Normal file
View File

@ -0,0 +1,33 @@
version: 'v2.2.1-dev.{build}'
build: off
branches:
only:
- master
- develop
environment:
matrix:
- PYTHON: "C:\\Python35"
- PYTHON: "C:\\Python35-x64"
- PYTHON: "C:\\Python36"
- PYTHON: "C:\\Python36-x64"
- PYTHON: "C:\\Python37"
- PYTHON: "C:\\Python37-x64"
- PYTHON: "C:\\Python38"
- PYTHON: "C:\\Python38-x64"
matrix:
fast_finish: true
cache:
- '%LOCALAPPDATA%\pip\Cache'
- .downloads -> .appveyor.yml
install:
- "cmd /c .\\test\\tools\\ci-win.cmd install"
test_script:
- "cmd /c .\\test\\tools\\ci-win.cmd test"
on_failure:
- ps: get-content .tox\*\log\*

3
.dockerignore Normal file
View File

@ -0,0 +1,3 @@
src/ssh_audit/__pycache__/
src/ssh_audit.egg-info/
src/ssh_audit/*~

27
.gitignore vendored
View File

@ -1,2 +1,27 @@
*~
*.pyc
*.py[cod]
*.exe
*.asc
venv*/
.cache/
.mypy_cache/
.tox
.coverage*
reports/
.scannerwork/
# PyPI packaging
/build/
/dist/
*.egg-info/
*.egg
# Snap packaging
/parts/
/prime/
/snap/
/stage/
/ssh-audit_*.snap
# Your local server config
servers.txt

View File

@ -1,18 +1,24 @@
language: python
python:
- 2.6
- 2.7
- 3.3
- 3.4
- 3.5
- pypy
- pypy3
install:
- pip install --upgrade pytest
- pip install --upgrade pytest-cov
- pip install --upgrade coveralls
script:
- py.test --cov-report= --cov=ssh-audit -v test
after_success:
- coveralls
arch:
- arm64
- amd64
- ppc64le
python:
- "3.6"
- "3.7"
- "3.8"
- "3.9"
cache:
- pip
install:
- pip install -U pip tox tox-travis coveralls codecov
script:
- tox
after_success:
- codecov

44
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,44 @@
# Contributing to ssh-audit
We are very much open to receiving patches from the community! To encourage participation, passing Travis tests, unit tests, etc., *is OPTIONAL*. As long as the patch works properly, it can be merged.
However, if you can submit patches that pass all of our automated tests, then you'll lighten the load for the project maintainer (who already has enough to do!). This document describes what tests are done and what documentation is maintained.
*Anything extra you can do is appreciated!*
## Tox Tests
Tox is used to do unit testing, linting with [pylint](http://pylint.pycqa.org/en/latest/) & [flake8](https://flake8.pycqa.org/en/latest/), and static type-checking with [mypy](https://mypy.readthedocs.io/en/stable/).
### Running tests on Ubuntu 18.04 and later
For Ubuntu 18.04 or later, install tox with `apt install tox`, then simply run `tox` in the top-level directory. Look for any error messages in the (verbose) output.
### Running tests on Ubuntu 16.04
For Ubuntu 16.04 (which is still supported until April 2021), a newer version of tox is needed. The easiest way is to use virtualenv:
```
$ sudo apt install python3-virtualenv
$ virtualenv -p /usr/bin/python3 ~/venv_ssh-audit
$ source ~/venv_ssh-audit/bin/activate
$ pip install tox
```
Then, to run the tox tests:
```
$ source ~/venv_ssh-audit/bin/activate
$ cd path/to/ssh-audit
$ tox
```
## Docker Tests
Docker is used to run ssh-audit against various real SSH servers (OpenSSH, Dropbear, and TinySSH). The output is then diff'ed against the expected result. Any differences result in failure.
The docker tests are run with `./docker_test.sh`. The first time it is run, it will download and compile the SSH servers; this may take awhile. Subsequent runs, however, will take only a minute to complete, as the docker image will already be up-to-date.
## Man Page
The `ssh-audit.1` man page documents the various features of ssh-audit. If features are added, or significant behavior is modified, the man page needs to be updated.

10
Dockerfile Normal file
View File

@ -0,0 +1,10 @@
FROM python:3.9-slim
WORKDIR /
COPY ssh-audit.py .
COPY src/ .
ENTRYPOINT ["python3", "/ssh-audit.py"]
EXPOSE 2222

23
LICENSE Normal file
View File

@ -0,0 +1,23 @@
The MIT License (MIT)
Copyright (C) 2017-2020 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

13
Makefile.docker Normal file
View File

@ -0,0 +1,13 @@
VERSION = $(shell grep VERSION src/ssh_audit/globals.py | grep -E -o "'(v.*)'" | tr -d "'")
ifeq ($(VERSION),)
$(error "could not determine version!")
endif
all:
docker build -t positronsecurity/ssh-audit:${VERSION} .
docker tag positronsecurity/ssh-audit:${VERSION} positronsecurity/ssh-audit:latest
upload:
docker login
docker push positronsecurity/ssh-audit:${VERSION}
docker push positronsecurity/ssh-audit:latest

15
Makefile.pypi Normal file
View File

@ -0,0 +1,15 @@
all:
rm -rf /tmp/pypi_upload
virtualenv -p /usr/bin/python3 /tmp/pypi_upload/
cp -R src /tmp/pypi_upload/
cp setup.py setup.cfg README.md LICENSE /tmp/pypi_upload/
/bin/bash -c "pushd /tmp/pypi_upload/; source bin/activate; pip3 install setuptools twine; python3 setup.py sdist bdist_wheel"
uploadtest:
/bin/bash -c "pushd /tmp/pypi_upload; source bin/activate; twine upload --repository-url https://test.pypi.org/legacy/ /tmp/pypi_upload/dist/*"
uploadprod:
/bin/bash -c "pushd /tmp/pypi_upload; source bin/activate; twine upload /tmp/pypi_upload/dist/*"
clean:
rm -rf /tmp/pypi_upload/

6
Makefile.snap Normal file
View File

@ -0,0 +1,6 @@
all:
echo -e "\n\nDid you remember to bump the version number in snapcraft.yaml?\n\n"
snapcraft --use-lxd
clean:
rm -rf parts/ prime/ snap/ stage/ build/ dist/ src/*.egg-info/ ssh-audit*.snap

98
PACKAGING.md Normal file
View File

@ -0,0 +1,98 @@
# Windows
An executable can only be made on a Windows host because the PyInstaller tool (https://www.pyinstaller.org/) does not support cross-compilation.
On a Windows machine, do the following:
1.) Install Python v3.9.x from https://www.python.org/. To make life easier, check the option to add Python to the PATH environment variable.
2.) Using pip, install pyinstaller and colorama:
```
pip install pyinstaller colorama
```
3.) Create the executable with:
```
cd src\ssh_audit
rename ssh_audit.py ssh-audit.py
pyinstaller -F --icon ..\..\windows_icon.ico ssh-audit.py
```
# PyPI
To create package and upload to test server:
```
$ sudo apt install python3-virtualenv
$ make -f Makefile.pypi
$ make -f Makefile.pypi uploadtest
```
To download from test server and verify:
```
$ virtualenv -p /usr/bin/python3 /tmp/pypi_test
$ cd /tmp/pypi_test; source bin/activate
$ pip3 install --index-url https://test.pypi.org/simple ssh-audit
```
To upload to production server (hint: use username '__token__' and API token):
```
$ make -f Makefile.pypi uploadprod
```
To download from production server and verify:
```
$ virtualenv -p /usr/bin/python3 /tmp/pypi_prod
$ cd /tmp/pypi_prod; source bin/activate
$ pip3 install ssh-audit
```
# Snap
To create the snap package, run a fully-updated Ubuntu Server 20.04 VM.
Install pre-requisites with:
```
$ sudo apt install make snapcraft
$ sudo snap install review-tools
```
Initialize LXD (leave all options default):
```
$ sudo lxd init
```
Bump the version number in snapcraft.yaml. Then run:
```
$ make -f Makefile.snap
```
Upload the snap with:
```
$ snapcraft login
$ snapcraft upload --release=stable ssh-audit_*.snap
```
# Docker
Build image with:
```
$ make -f Makefile.docker
```
Then upload them to Dockerhub with:
```
$ make -f Makefile.docker upload
```

229
README.md
View File

@ -1,9 +1,17 @@
# ssh-audit
[![build status](https://api.travis-ci.org/arthepsy/ssh-audit.svg)](https://travis-ci.org/arthepsy/ssh-audit)
**ssh-audit** is a tool for ssh server auditing.
[![Build Status](https://travis-ci.org/jtesta/ssh-audit.svg?branch=master)](https://travis-ci.org/jtesta/ssh-audit)
<!--
[![appveyor build status](https://ci.appveyor.com/api/projects/status/4m5r73m0r023edil/branch/develop?svg=true)](https://ci.appveyor.com/project/arthepsy/ssh-audit)
[![codecov](https://codecov.io/gh/arthepsy/ssh-audit/branch/develop/graph/badge.svg)](https://codecov.io/gh/arthepsy/ssh-audit)
[![Quality Gate](https://sonarqube.com/api/badges/gate?key=arthepsy-github%3Assh-audit%3Adevelop&template=ROUNDED)](https://sq.evolutiongaming.com/dashboard?id=arthepsy-github%3Assh-audit%3Adevelop)
-->
**ssh-audit** is a tool for ssh server & client configuration auditing.
[jtesta/ssh-audit](https://github.com/jtesta/ssh-audit/) (v2.0+) is the updated and maintained version of ssh-audit forked from [arthepsy/ssh-audit](https://github.com/arthepsy/ssh-audit) (v1.x) due to inactivity.
## Features
- SSH1 and SSH2 protocol server support;
- analyze SSH client configuration;
- grab banner, recognize device or software and operating system, detect compression;
- gather key-exchange, host-key, encryption and message authentication code algorithms;
- output algorithm information (available since, removed/disabled, unsafe/weak/legacy, etc);
@ -11,27 +19,228 @@
- output security information (related issues, assigned CVE list, etc);
- analyze SSH version compatibility based on algorithm information;
- historical information from OpenSSH, Dropbear SSH and libssh;
- no dependencies, compatible with Python 2.6+, Python 3.x and PyPy;
- policy scans to ensure adherence to a hardened/standard configuration;
- runs on Linux and Windows;
- supports Python 3.6 - 3.9;
- no dependencies
## Usage
```
usage: ssh-audit.py [-bnv] [-l <level>] <host[:port]>
usage: ssh-audit.py [options] <host>
-h, --help print this help
-1, --ssh1 force ssh version 1 only
-2, --ssh2 force ssh version 2 only
-4, --ipv4 enable IPv4 (order of precedence)
-6, --ipv6 enable IPv6 (order of precedence)
-b, --batch batch output
-n, --no-colors disable colors
-v, --verbose verbose output
-c, --client-audit starts a server on port 2222 to audit client
software config (use -p to change port;
use -t to change timeout)
-j, --json JSON output
-l, --level=<level> minimum output level (info|warn|fail)
-L, --list-policies list all the official, built-in policies
--lookup=<alg1,alg2,...> looks up an algorithm(s) without
connecting to a server
-m, --manual print the man page (Windows only)
-M, --make-policy=<policy.txt> creates a policy based on the target server
(i.e.: the target server has the ideal
configuration that other servers should
adhere to)
-n, --no-colors disable colors
-p, --port=<port> port to connect
-P, --policy=<"policy name" | policy.txt> run a policy test using the
specified policy
-t, --timeout=<secs> timeout (in seconds) for connection and reading
(default: 5)
-T, --targets=<hosts.txt> a file containing a list of target hosts (one
per line, format HOST[:PORT])
--threads=<threads> number of threads to use when scanning multiple
targets (-T/--targets) (default: 32)
-v, --verbose verbose output
```
* if both IPv4 and IPv6 are used, order of precedence can be set by using either `-46` or `-64`.
* batch flag `-b` will output sections without header and without empty lines (implies verbose flag).
* verbose flag `-v` will prefix each line with section type and algorithm name.
* an exit code of 0 is returned when all algorithms are considered secure (for a standard audit), or when a policy check passes (for a policy audit).
### example
![screenshot](https://cloud.githubusercontent.com/assets/7356025/19233757/3e09b168-8ef0-11e6-91b4-e880bacd0b8a.png)
Basic server auditing:
```
ssh-audit localhost
ssh-audit 127.0.0.1
ssh-audit 127.0.0.1:222
ssh-audit ::1
ssh-audit [::1]:222
```
To run a standard audit against many servers (place targets into servers.txt, one on each line in the format of `HOST[:PORT]`):
```
ssh-audit -T servers.txt
```
To audit a client configuration (listens on port 2222 by default; connect using `ssh -p 2222 anything@localhost`):
```
ssh-audit -c
```
To audit a client configuration, with a listener on port 4567:
```
ssh-audit -c -p 4567
```
To list all official built-in policies (hint: use resulting policy names with `-P`/`--policy`):
```
ssh-audit -L
```
To run a policy audit against a server:
```
ssh-audit -P ["policy name" | path/to/server_policy.txt] targetserver
```
To run a policy audit against a client:
```
ssh-audit -c -P ["policy name" | path/to/client_policy.txt]
```
To run a policy audit against many servers:
```
ssh-audit -T servers.txt -P ["policy name" | path/to/server_policy.txt]
```
To create a policy based on a target server (which can be manually edited):
```
ssh-audit -M new_policy.txt targetserver
```
### Server Standard Audit Example
Below is a screen shot of the standard server-auditing output when connecting to an unhardened OpenSSH v5.3 service:
![screenshot](https://user-images.githubusercontent.com/2982011/64388792-317e6f80-d00e-11e9-826e-a4934769bb07.png)
### Server Policy Audit Example
Below is a screen shot of the policy auditing output when connecting to an un-hardened Ubuntu Server 20.04 machine (hint: use `-L`/`--list-policies` to see names of built-in policies to use with `-P`/`--policy`):
![screenshot](https://user-images.githubusercontent.com/2982011/94370881-95178700-00c0-11eb-8705-3157a4669dc0.png)
After applying the steps in the hardening guide (see below), the output changes to the following:
![screenshot](https://user-images.githubusercontent.com/2982011/94370873-87620180-00c0-11eb-9a59-469f61a56ce1.png)
### Client Standard Audit Example
Below is a screen shot of the client-auditing output when an unhardened OpenSSH v7.2 client connects:
![client_screenshot](https://user-images.githubusercontent.com/2982011/68867998-b946c100-06c4-11ea-975f-1f47e4178a74.png)
### Hardening Guides
Guides to harden server & client configuration can be found here: [https://www.ssh-audit.com/hardening_guides.html](https://www.ssh-audit.com/hardening_guides.html)
### Pre-Built Packages
Pre-built packages are available for Windows (see the releases page), on PyPI, Snap, and Homebrew.
To install from PyPI:
```
$ pip3 install ssh-audit
```
To install the Snap package:
```
$ snap install ssh-audit
```
To install on Homebrew:
```
$ brew install ssh-audit
```
To install from Dockerhub:
```
$ docker pull positronsecurity/ssh-audit
```
(Then run with: `docker run -it -p 2222:2222 positronsecurity/ssh-audit 10.1.1.1`)
### Web Front-End
For convenience, a web front-end on top of the command-line tool is available at [https://www.ssh-audit.com/](https://www.ssh-audit.com/).
## ChangeLog
### v2.4.0 (2021-02-23)
- Added multi-threaded scanning support.
- Added built-in Windows manual page (see `-m`/`--manual`); credit [Adam Russell](https://github.com/thecliguy).
- Added version check for OpenSSH user enumeration (CVE-2018-15473).
- Added deprecation note to host key types based on SHA-1.
- Added extra warnings for SSHv1.
- Added built-in hardened OpenSSH v8.5 policy.
- Upgraded warnings to failures for host key types based on SHA-1.
- Fixed crash when receiving unexpected response during host key test.
- Fixed hang against older Cisco devices during host key test & gex test.
- Fixed improper termination while scanning multiple targets when one target returns an error.
- Dropped support for Python 3.5 (which reached EOL in Sept. 2020).
- Added 1 new key exchange: `sntrup761x25519-sha512@openssh.com`.
### v2.3.1 (2020-10-28)
- Now parses public key sizes for `rsa-sha2-256-cert-v01@openssh.com` and `rsa-sha2-512-cert-v01@openssh.com` host key types.
- Flag `ssh-rsa-cert-v01@openssh.com` as a failure due to SHA-1 hash.
- Fixed bug in recommendation output which suppressed some algorithms inappropriately.
- Built-in policies now include CA key requirements (if certificates are in use).
- Lookup function (`--lookup`) now performs case-insensitive lookups of similar algorithms; credit [Adam Russell](https://github.com/thecliguy).
- Migrated pre-made policies from external files to internal database.
- Split single 3,500 line script into many files (by class).
- Added setup.py support; credit [Ganden Schaffner](https://github.com/gschaffner).
- Added 1 new cipher: `des-cbc@ssh.com`.
### v2.3.0 (2020-09-27)
- Added new policy auditing functionality to test adherence to a hardening guide/standard configuration (see `-L`/`--list-policies`, `-M`/`--make-policy` and `-P`/`--policy`). For an in-depth tutorial, see <https://www.positronsecurity.com/blog/2020-09-27-ssh-policy-configuration-checks-with-ssh-audit/>.
- Created new man page (see `ssh-audit.1` file).
- 1024-bit moduli upgraded from warnings to failures.
- Many Python 2 code clean-ups, testing framework improvements, pylint & flake8 fixes, and mypy type comments; credit [Jürgen Gmach](https://github.com/jugmac00).
- Added feature to look up algorithms in internal database (see `--lookup`); credit [Adam Russell](https://github.com/thecliguy).
- Suppress recommendation of token host key types.
- Added check for use-after-free vulnerability in PuTTY v0.73.
- Added 11 new host key types: `ssh-rsa1`, `ssh-dss-sha256@ssh.com`, `ssh-gost2001`, `ssh-gost2012-256`, `ssh-gost2012-512`, `spki-sign-rsa`, `ssh-ed448`, `x509v3-ecdsa-sha2-nistp256`, `x509v3-ecdsa-sha2-nistp384`, `x509v3-ecdsa-sha2-nistp521`, `x509v3-rsa2048-sha256`.
- Added 8 new key exchanges: `diffie-hellman-group1-sha256`, `kexAlgoCurve25519SHA256`, `Curve25519SHA256`, `gss-group14-sha256-`, `gss-group15-sha512-`, `gss-group16-sha512-`, `gss-nistp256-sha256-`, `gss-curve25519-sha256-`.
- Added 5 new ciphers: `blowfish`, `AEAD_AES_128_GCM`, `AEAD_AES_256_GCM`, `crypticore128@ssh.com`, `seed-cbc@ssh.com`.
- Added 3 new MACs: `chacha20-poly1305@openssh.com`, `hmac-sha3-224`, `crypticore-mac@ssh.com`.
### v2.2.0 (2020-03-11)
- Marked host key type `ssh-rsa` as weak due to [practical SHA-1 collisions](https://eprint.iacr.org/2020/014.pdf).
- Added Windows builds.
- Added 10 new host key types: `ecdsa-sha2-1.3.132.0.10`, `x509v3-sign-dss`, `x509v3-sign-rsa`, `x509v3-sign-rsa-sha256@ssh.com`, `x509v3-ssh-dss`, `x509v3-ssh-rsa`, `sk-ecdsa-sha2-nistp256-cert-v01@openssh.com`, `sk-ecdsa-sha2-nistp256@openssh.com`, `sk-ssh-ed25519-cert-v01@openssh.com`, and `sk-ssh-ed25519@openssh.com`.
- Added 18 new key exchanges: `diffie-hellman-group14-sha256@ssh.com`, `diffie-hellman-group15-sha256@ssh.com`, `diffie-hellman-group15-sha384@ssh.com`, `diffie-hellman-group16-sha384@ssh.com`, `diffie-hellman-group16-sha512@ssh.com`, `diffie-hellman-group18-sha512@ssh.com`, `ecdh-sha2-curve25519`, `ecdh-sha2-nistb233`, `ecdh-sha2-nistb409`, `ecdh-sha2-nistk163`, `ecdh-sha2-nistk233`, `ecdh-sha2-nistk283`, `ecdh-sha2-nistk409`, `ecdh-sha2-nistp192`, `ecdh-sha2-nistp224`, `ecdh-sha2-nistt571`, `gss-gex-sha1-`, and `gss-group1-sha1-`.
- Added 9 new ciphers: `camellia128-cbc`, `camellia128-ctr`, `camellia192-cbc`, `camellia192-ctr`, `camellia256-cbc`, `camellia256-ctr`, `aes128-gcm`, `aes256-gcm`, and `chacha20-poly1305`.
- Added 2 new MACs: `aes128-gcm` and `aes256-gcm`.
### v2.1.1 (2019-11-26)
- Added 2 new host key types: `rsa-sha2-256-cert-v01@openssh.com`, `rsa-sha2-512-cert-v01@openssh.com`.
- Added 2 new ciphers: `des`, `3des`.
- Added 3 new PuTTY vulnerabilities.
- During client testing, client IP address is now listed in output.
### v2.1.0 (2019-11-14)
- Added client software auditing functionality (see `-c` / `--client-audit` option).
- Added JSON output option (see `-j` / `--json` option; credit [Andreas Jaggi](https://github.com/x-way)).
- Fixed crash while scanning Solaris Sun_SSH.
- Added 9 new key exchanges: `gss-group1-sha1-toWM5Slw5Ew8Mqkay+al2g==`, `gss-gex-sha1-toWM5Slw5Ew8Mqkay+al2g==`, `gss-group14-sha1-`, `gss-group14-sha1-toWM5Slw5Ew8Mqkay+al2g==`, `gss-group14-sha256-toWM5Slw5Ew8Mqkay+al2g==`, `gss-group15-sha512-toWM5Slw5Ew8Mqkay+al2g==`, `diffie-hellman-group15-sha256`, `ecdh-sha2-1.3.132.0.10`, `curve448-sha512`.
- Added 1 new host key type: `ecdsa-sha2-1.3.132.0.10`.
- Added 4 new ciphers: `idea-cbc`, `serpent128-cbc`, `serpent192-cbc`, `serpent256-cbc`.
- Added 6 new MACs: `hmac-sha2-256-96-etm@openssh.com`, `hmac-sha2-512-96-etm@openssh.com`, `hmac-ripemd`, `hmac-sha256-96@ssh.com`, `umac-32@openssh.com`, `umac-96@openssh.com`.
### v2.0.0 (2019-08-29)
- Forked from https://github.com/arthepsy/ssh-audit (development was stalled, and developer went MIA).
- Added RSA host key length test.
- Added RSA certificate key length test.
- Added Diffie-Hellman modulus size test.
- Now outputs host key fingerprints for RSA and ED25519.
- Added 5 new key exchanges: `sntrup4591761x25519-sha512@tinyssh.org`, `diffie-hellman-group-exchange-sha256@ssh.com`, `diffie-hellman-group-exchange-sha512@ssh.com`, `diffie-hellman-group16-sha256`, `diffie-hellman-group17-sha512`.
- Added 3 new encryption algorithms: `des-cbc-ssh1`, `blowfish-ctr`, `twofish-ctr`.
- Added 10 new MACs: `hmac-sha2-56`, `hmac-sha2-224`, `hmac-sha2-384`, `hmac-sha3-256`, `hmac-sha3-384`, `hmac-sha3-512`, `hmac-sha256`, `hmac-sha256@ssh.com`, `hmac-sha512`, `hmac-512@ssh.com`.
- Added command line argument (`-t` / `--timeout`) for connection & reading timeouts.
- Updated CVEs for libssh & Dropbear.
### v1.7.0 (2016-10-26)
- implement options to allow specify IPv4/IPv6 usage and order of precedence
- implement option to specify remote port (old behavior kept for compatibility)
- add colors support for Microsoft Windows via optional colorama dependency
- fix encoding and decoding issues, add tests, do not crash on encoding errors
- use mypy-lang for static type checking and verify all code
### v1.6.0 (2016-10-14)
- implement algorithm recommendations section (based on recognized software)
- implement full libssh support (version history, algorithms, security, etc)
@ -47,7 +256,7 @@ usage: ssh-audit.py [-bnv] [-l <level>] <host[:port]>
- implement full SSH1 support with fingerprint information
- automatically fallback to SSH1 on protocol mismatch
- add new options to force SSH1 or SSH2 (both allowed by default)
- parse banner information and convert it to specific sofware and OS version
- parse banner information and convert it to specific software and OS version
- do not use padding in batch mode
- several fixes (Cisco sshd, rare hangs, error handling, etc)

92
build_windows_executable.sh Executable file
View File

@ -0,0 +1,92 @@
#!/bin/bash
#
# The MIT License (MIT)
#
# Copyright (C) 2021 Joe Testa (jtesta@positronsecurity.com)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
################################################################################
# build_windows_executable.sh
#
# Builds a Windows executable using PyInstaller.
################################################################################
PLATFORM="$(uname -s)"
# This script is intended for use on Cygwin only.
case "$PLATFORM" in
CYGWIN*) ;;
*)
echo "Platform not supported ($PLATFORM). This must be run in Cygwin only."
exit 1
;;
esac
# Ensure that Python 3.x is installed.
if [[ "$(python -V)" != "Python 3."* ]]; then
echo "Python v3.x not found. Install the latest stable version from: https://www.python.org/"
exit 1
fi
# Ensure that pyinstaller is installed.
command -v pyinstaller >/dev/null 2>&1 || { echo >&2 "pyinstaller not found. Install with: 'pip install pyinstaller'"; exit 1; }
# Ensure that the colorama module is installed.
X=`pip show colorama` 2> /dev/null
if [[ $? != 0 ]]; then
echo "Colorama module not found. Install with: 'pip install colorama'"
exit 1
fi
# Update the man page.
./update_windows_man_page.sh
if [[ $? != 0 ]]; then
echo "Failed to run ./update_windows_man_page.sh"
exit 1
fi
# Do all operations from this point from the main source directory.
pushd src/ssh_audit > /dev/null
# Delete the executable if it exists from a prior run.
if [[ -f dist/ssh-audit.exe ]]; then
rm dist/ssh-audit.exe
fi
# Create a link from ssh_audit.py to ssh-audit.py.
if [[ ! -f ssh-audit.py ]]; then
ln ssh_audit.py ssh-audit.py
fi
echo -e "\nRunning pyinstaller...\n"
pyinstaller -F --icon ../../windows_icon.ico ssh-audit.py
if [[ -f dist/ssh-audit.exe ]]; then
echo -e "\nExecutable created in $(pwd)/dist/ssh-audit.exe\n"
fi
# Remove the link we created, above.
rm ssh-audit.py
popd > /dev/null
exit 0

716
docker_test.sh Executable file
View File

@ -0,0 +1,716 @@
#!/bin/bash
#
# This script will set up a docker image with multiple versions of OpenSSH, then
# use it to run tests.
#
# For debugging purposes, here is a cheat sheet for manually running the docker image:
#
# docker run -p 2222:22 -it ssh-audit-test:X /bin/bash
# docker run -p 2222:22 --security-opt seccomp:unconfined -it ssh-audit-test /debug.sh
# docker run -d -p 2222:22 ssh-audit-test:X /openssh/sshd-5.6p1 -D -f /etc/ssh/sshd_config-5.6p1_test1
# docker run -d -p 2222:22 ssh-audit-test:X /openssh/sshd-8.0p1 -D -f /etc/ssh/sshd_config-8.0p1_test1
#
# This is the docker tag for the image. If this tag doesn't exist, then we assume the
# image is out of date, and generate a new one with this tag.
IMAGE_VERSION=3
# This is the name of our docker image.
IMAGE_NAME=positronsecurity/ssh-audit-test-framework
# Terminal colors.
CLR="\033[0m"
RED="\033[0;31m"
YELLOW="\033[0;33m"
GREEN="\033[0;32m"
REDB="\033[1;31m" # Red + bold
GREENB="\033[1;32m" # Green + bold
# Program return values.
PROGRAM_RETVAL_FAILURE=3
PROGRAM_RETVAL_WARNING=2
PROGRAM_RETVAL_CONNECTION_ERROR=1
PROGRAM_RETVAL_GOOD=0
# Counts the number of test failures.
num_failures=0
# Returns 0 if current docker image exists.
function check_if_docker_image_exists {
images=`docker image ls | egrep "$IMAGE_NAME[[:space:]]+$IMAGE_VERSION"`
}
# Uncompresses and compiles the specified version of Dropbear.
function compile_dropbear {
version=$1
compile 'Dropbear' $version
}
# Uncompresses and compiles the specified version of OpenSSH.
function compile_openssh {
version=$1
compile 'OpenSSH' $version
}
# Uncompresses and compiles the specified version of TinySSH.
function compile_tinyssh {
version=$1
compile 'TinySSH' $version
}
function compile {
project=$1
version=$2
tarball=
uncompress_options=
source_dir=
server_executable=
if [[ $project == 'OpenSSH' ]]; then
tarball="openssh-${version}.tar.gz"
uncompress_options="xzf"
source_dir="openssh-${version}"
server_executable=sshd
elif [[ $project == 'Dropbear' ]]; then
tarball="dropbear-${version}.tar.bz2"
uncompress_options="xjf"
source_dir="dropbear-${version}"
server_executable=dropbear
elif [[ $project == 'TinySSH' ]]; then
tarball="${version}.tar.gz"
uncompress_options="xzf"
source_dir="tinyssh-${version}"
server_executable='build/bin/tinysshd'
fi
echo "Uncompressing ${project} ${version}..."
tar $uncompress_options $tarball
echo "Compiling ${project} ${version}..."
pushd $source_dir > /dev/null
# TinySSH has no configure script... only a Makefile.
if [[ $project == 'TinySSH' ]]; then
make -j 10
else
./configure && make -j 10
fi
if [[ ! -f $server_executable ]]; then
echo -e "${REDB}Error: ${server_executable} not built!${CLR}"
exit 1
fi
echo -e "\n${GREEN}Successfully built ${project} ${version}${CLR}\n"
popd > /dev/null
}
# Creates a new docker image.
function create_docker_image {
# Create a new temporary directory.
TMP_DIR=`mktemp -d /tmp/sshaudit-docker-XXXXXXXXXX`
# Copy the Dockerfile and all files in the test/docker/ dir to our new temp directory.
find test/docker/ -maxdepth 1 -type f | xargs cp -t $TMP_DIR
# Make the temp directory our working directory for the duration of the build
# process.
pushd $TMP_DIR > /dev/null
# Get the release keys.
get_dropbear_release_key
get_openssh_release_key
get_tinyssh_release_key
# Aside from checking the GPG signatures, we also compare against this known-good
# SHA-256 hash just in case.
get_openssh '4.0p1' '5adb9b2c2002650e15216bf94ed9db9541d9a17c96fcd876784861a8890bc92b'
get_openssh '5.6p1' '538af53b2b8162c21a293bb004ae2bdb141abd250f61b4cea55244749f3c6c2b'
get_openssh '8.0p1' 'bd943879e69498e8031eb6b7f44d08cdc37d59a7ab689aa0b437320c3481fd68'
get_dropbear '2019.78' '525965971272270995364a0eb01f35180d793182e63dd0b0c3eb0292291644a4'
get_tinyssh '20190101' '554a9a94e53b370f0cd0c5fbbd322c34d1f695cbcea6a6a32dcb8c9f595b3fea'
# Compile the versions of OpenSSH.
compile_openssh '4.0p1'
compile_openssh '5.6p1'
compile_openssh '8.0p1'
# Compile the versions of Dropbear.
compile_dropbear '2019.78'
# Compile the versions of TinySSH.
compile_tinyssh '20190101'
# Rename the default config files so we know they are our originals.
mv openssh-4.0p1/sshd_config sshd_config-4.0p1_orig
mv openssh-5.6p1/sshd_config sshd_config-5.6p1_orig
mv openssh-8.0p1/sshd_config sshd_config-8.0p1_orig
# Create the configurations for each test.
#
# OpenSSH v4.0p1
#
# Test 1: Basic test.
create_openssh_config '4.0p1' 'test1' "HostKey /etc/ssh/ssh1_host_key\nHostKey /etc/ssh/ssh_host_rsa_key_1024\nHostKey /etc/ssh/ssh_host_dsa_key"
#
# OpenSSH v5.6p1
#
# Test 1: Basic test.
create_openssh_config '5.6p1' 'test1' "HostKey /etc/ssh/ssh_host_rsa_key_1024\nHostKey /etc/ssh/ssh_host_dsa_key"
# Test 2: RSA 1024 host key with RSA 1024 certificate.
create_openssh_config '5.6p1' 'test2' "HostKey /etc/ssh/ssh_host_rsa_key_1024\nHostCertificate /etc/ssh/ssh_host_rsa_key_1024-cert_1024.pub"
# Test 3: RSA 1024 host key with RSA 3072 certificate.
create_openssh_config '5.6p1' 'test3' "HostKey /etc/ssh/ssh_host_rsa_key_1024\nHostCertificate /etc/ssh/ssh_host_rsa_key_1024-cert_3072.pub"
# Test 4: RSA 3072 host key with RSA 1024 certificate.
create_openssh_config '5.6p1' 'test4' "HostKey /etc/ssh/ssh_host_rsa_key_3072\nHostCertificate /etc/ssh/ssh_host_rsa_key_3072-cert_1024.pub"
# Test 5: RSA 3072 host key with RSA 3072 certificate.
create_openssh_config '5.6p1' 'test5' "HostKey /etc/ssh/ssh_host_rsa_key_3072\nHostCertificate /etc/ssh/ssh_host_rsa_key_3072-cert_3072.pub"
#
# OpenSSH v8.0p1
#
# Test 1: Basic test.
create_openssh_config '8.0p1' 'test1' "HostKey /etc/ssh/ssh_host_rsa_key_3072\nHostKey /etc/ssh/ssh_host_ecdsa_key\nHostKey /etc/ssh/ssh_host_ed25519_key"
# Test 2: ED25519 certificate test.
create_openssh_config '8.0p1' 'test2' "HostKey /etc/ssh/ssh_host_ed25519_key\nHostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub"
# Test 3: Hardened installation test.
create_openssh_config '8.0p1' 'test3' "HostKey /etc/ssh/ssh_host_ed25519_key\nKexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com"
# Now build the docker image!
docker build --tag $IMAGE_NAME:$IMAGE_VERSION .
popd > /dev/null
rm -rf $TMP_DIR
}
# Creates an OpenSSH configuration file for a specific test.
function create_openssh_config {
openssh_version=$1
test_number=$2
config_text=$3
cp sshd_config-${openssh_version}_orig sshd_config-${openssh_version}_${test_number}
echo -e "${config_text}" >> sshd_config-${openssh_version}_${test_number}
}
# Downloads the Dropbear release key and adds it to the local keyring.
function get_dropbear_release_key {
get_release_key 'Dropbear' 'https://matt.ucc.asn.au/dropbear/releases/dropbear-key-2015.asc' 'F29C6773' 'F734 7EF2 EE2E 07A2 6762 8CA9 4493 1494 F29C 6773'
}
# Downloads the OpenSSH release key and adds it to the local keyring.
function get_openssh_release_key {
get_release_key 'OpenSSH' 'https://ftp.openbsd.org/pub/OpenBSD/OpenSSH/RELEASE_KEY.asc' '6D920D30' '59C2 118E D206 D927 E667 EBE3 D3E5 F56B 6D92 0D30'
}
# Downloads the TinySSH release key and adds it to the local keyring.
function get_tinyssh_release_key {
get_release_key 'TinySSH' '' '96939FF9' 'AADF 2EDF 5529 F170 2772 C8A2 DEC4 D246 931E F49B'
}
function get_release_key {
project=$1
key_url=$2
key_id=$3
release_key_fingerprint_expected=$4
# The TinySSH release key isn't on any website, apparently.
if [[ $project == 'TinySSH' ]]; then
gpg --keyserver keys.gnupg.net --recv-key $key_id
else
echo -e "\nGetting ${project} release key...\n"
wget -O key.asc $2
echo -e "\nImporting ${project} release key...\n"
gpg --import key.asc
rm key.asc
fi
local release_key_fingerprint_actual=`gpg --fingerprint ${key_id}`
if [[ $release_key_fingerprint_actual != *"$release_key_fingerprint_expected"* ]]; then
echo -e "\n${REDB}Error: ${project} release key fingerprint does not match expected value!\n\tExpected: $release_key_fingerprint_expected\n\tActual: $release_key_fingerprint_actual\n\nTerminating.${CLR}"
exit -1
fi
echo -e "\n\n${GREEN}${project} release key matches expected value.${CLR}\n"
}
# Downloads the specified version of Dropbear.
function get_dropbear {
version=$1
tarball_checksum_expected=$2
get_source 'Dropbear' $version $tarball_checksum_expected
}
# Downloads the specified version of OpenSSH.
function get_openssh {
version=$1
tarball_checksum_expected=$2
get_source 'OpenSSH' $version $tarball_checksum_expected
}
# Downloads the specified version of TinySSH.
function get_tinyssh {
version=$1
tarball_checksum_expected=$2
get_source 'TinySSH' $version $tarball_checksum_expected
}
function get_source {
project=$1
version=$2
tarball_checksum_expected=$3
base_url_source=
base_url_sig=
tarball=
sig=
signer=
if [[ $project == 'OpenSSH' ]]; then
base_url_source='https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/'
base_url_sig=$base_url_source
tarball="openssh-${version}.tar.gz"
sig="${tarball}.asc"
signer="Damien Miller "
elif [[ $project == 'Dropbear' ]]; then
base_url_source='https://matt.ucc.asn.au/dropbear/releases/'
base_url_sig=$base_url_source
tarball="dropbear-${version}.tar.bz2"
sig="${tarball}.asc"
signer="Dropbear SSH Release Signing <matt@ucc.asn.au>"
elif [[ $project == 'TinySSH' ]]; then
base_url_source='https://github.com/janmojzis/tinyssh/archive/'
base_url_sig="https://github.com/janmojzis/tinyssh/releases/download/${version}/"
tarball="${version}.tar.gz"
sig="${tarball}.asc"
signer="Jan Mojžíš <jan.mojzis@gmail.com>"
fi
echo -e "\nGetting ${project} ${version} sources...\n"
wget "${base_url_source}${tarball}"
echo -e "\nGetting ${project} ${version} signature...\n"
wget "${base_url_sig}${sig}"
# Older OpenSSH releases were .sigs.
if [[ ($project == 'OpenSSH') && (! -f $sig) ]]; then
wget ${base_url_sig}openssh-${version}.tar.gz.sig
sig=openssh-${version}.tar.gz.sig
fi
local gpg_verify=`gpg --verify ${sig} ${tarball} 2>&1`
if [[ $gpg_verify != *"Good signature from \"${signer}"* ]]; then
echo -e "\n\n${REDB}Error: ${project} signature invalid!\n$gpg_verify\n\nTerminating.${CLR}"
exit -1
fi
# Check GPG's return value. 0 denotes a valid signature, and 1 is returned
# on invalid signatures.
if [[ $? != 0 ]]; then
echo -e "\n\n${REDB}Error: ${project} signature invalid! Verification returned code: $?\n\nTerminating.${CLR}"
exit -1
fi
echo -e "${GREEN}Signature on ${project} sources verified.${CLR}\n"
local checksum_actual=`sha256sum ${tarball} | cut -f1 -d" "`
if [[ $checksum_actual != $tarball_checksum_expected ]]; then
echo -e "${REDB}Error: ${project} checksum is invalid!\n Expected: ${tarball_checksum_expected}\n Actual: ${checksum_actual}\n\n Terminating.${CLR}"
exit -1
fi
}
# Pulls the defined image from Dockerhub.
function pull_docker_image {
docker pull $IMAGE_NAME:$IMAGE_VERSION
if [[ $? == 0 ]]; then
echo -e "${GREEN}Successfully downloaded image ${IMAGE_NAME}:${IMAGE_VERSION} from Dockerhub.${CLR}\n"
else
echo -e "${REDB}Failed to pull image ${IMAGE_NAME}:${IMAGE_VERSION} from Dockerhub! Error code: $?${CLR}\n"
exit -1
fi
}
# Runs a Dropbear test. Upon failure, a diff between the expected and actual results
# is shown, then the script immediately terminates.
function run_dropbear_test {
dropbear_version=$1
test_number=$2
options=$3
expected_retval=$4
run_test 'Dropbear' $dropbear_version $test_number "$options" $expected_retval
}
# Runs an OpenSSH test. Upon failure, a diff between the expected and actual results
# is shown, then the script immediately terminates.
function run_openssh_test {
openssh_version=$1
test_number=$2
expected_retval=$3
run_test 'OpenSSH' $openssh_version $test_number '' $expected_retval
}
# Runs a TinySSH test. Upon failure, a diff between the expected and actual results
# is shown, then the script immediately terminates.
function run_tinyssh_test {
tinyssh_version=$1
test_number=$2
expected_retval=$3
run_test 'TinySSH' $tinyssh_version $test_number '' $expected_retval
}
function run_test {
server_type=$1
version=$2
test_number=$3
options=$4
expected_retval=$5
failed=0 # Set to 1 if this test fails.
server_exec=
test_result_stdout=
test_result_json=
expected_result_stdout=
expected_result_json=
test_name=
if [[ $server_type == 'OpenSSH' ]]; then
server_exec="/openssh/sshd-${version} -D -f /etc/ssh/sshd_config-${version}_${test_number}"
test_result_stdout="${TEST_RESULT_DIR}/openssh_${version}_${test_number}.txt"
test_result_json="${TEST_RESULT_DIR}/openssh_${version}_${test_number}.json"
expected_result_stdout="test/docker/expected_results/openssh_${version}_${test_number}.txt"
expected_result_json="test/docker/expected_results/openssh_${version}_${test_number}.json"
test_name="OpenSSH ${version} ${test_number}"
options=
elif [[ $server_type == 'Dropbear' ]]; then
server_exec="/dropbear/dropbear-${version} -F ${options}"
test_result_stdout="${TEST_RESULT_DIR}/dropbear_${version}_${test_number}.txt"
test_result_json="${TEST_RESULT_DIR}/dropbear_${version}_${test_number}.json"
expected_result_stdout="test/docker/expected_results/dropbear_${version}_${test_number}.txt"
expected_result_json="test/docker/expected_results/dropbear_${version}_${test_number}.json"
test_name="Dropbear ${version} ${test_number}"
elif [[ $server_type == 'TinySSH' ]]; then
server_exec="/usr/bin/tcpserver -HRDl0 0.0.0.0 22 /tinysshd/tinyssh-20190101 -v /etc/tinyssh/"
test_result_stdout="${TEST_RESULT_DIR}/tinyssh_${version}_${test_number}.txt"
test_result_json="${TEST_RESULT_DIR}/tinyssh_${version}_${test_number}.json"
expected_result_stdout="test/docker/expected_results/tinyssh_${version}_${test_number}.txt"
expected_result_json="test/docker/expected_results/tinyssh_${version}_${test_number}.json"
test_name="TinySSH ${version} ${test_number}"
fi
cid=`docker run -d -p 2222:22 ${IMAGE_NAME}:${IMAGE_VERSION} ${server_exec}`
#echo "Running: docker run -d -p 2222:22 ${IMAGE_NAME}:${IMAGE_VERSION} ${server_exec}"
if [[ $? != 0 ]]; then
echo -e "${REDB}Failed to run docker image! (exit code: $?)${CLR}"
exit 1
fi
./ssh-audit.py localhost:2222 > $test_result_stdout
actual_retval=$?
if [[ $actual_retval != $expected_retval ]]; then
echo -e "${REDB}Unexpected return value. Expected: ${expected_retval}; Actual: ${actual_retval}${CLR}"
docker container stop -t 0 $cid > /dev/null
exit 1
fi
./ssh-audit.py -j localhost:2222 > $test_result_json
actual_retval=$?
if [[ $actual_retval != $expected_retval ]]; then
echo -e "${REDB}Unexpected return value. Expected: ${expected_retval}; Actual: ${actual_retval}${CLR}"
docker container stop -t 0 $cid > /dev/null
exit 1
fi
docker container stop -t 0 $cid > /dev/null
if [[ $? != 0 ]]; then
echo -e "${REDB}Failed to stop docker container ${cid}! (exit code: $?)${CLR}"
exit 1
fi
# TinySSH outputs a random string in each banner, which breaks our test. So
# we need to filter out the banner part of the output so we get stable, repeatable
# results.
if [[ $server_type == 'TinySSH' ]]; then
grep -v "(gen) banner: " ${test_result_stdout} > "${test_result_stdout}.tmp"
mv "${test_result_stdout}.tmp" ${test_result_stdout}
cat "${test_result_json}" | perl -pe 's/"comments": ".*?"/"comments": ""/' | perl -pe 's/"raw": ".+?"/"raw": ""/' > "${test_result_json}.tmp"
mv "${test_result_json}.tmp" ${test_result_json}
fi
diff=`diff -u ${expected_result_stdout} ${test_result_stdout}`
if [[ $? != 0 ]]; then
echo -e "${test_name} ${REDB}FAILED${CLR}.\n\n${diff}\n"
failed=1
num_failures=$((num_failures+1))
fi
diff=`diff -u ${expected_result_json} ${test_result_json}`
if [[ $? != 0 ]]; then
echo -e "${test_name} ${REDB}FAILED${CLR}.\n\n${diff}\n"
failed=1
num_failures=$((num_failures+1))
fi
if [[ $failed == 0 ]]; then
echo -e "${test_name} ${GREEN}passed${CLR}."
fi
}
function run_builtin_policy_test {
policy_name=$1 # The built-in policy name to use.
version=$2 # Version of OpenSSH to test with.
test_number=$3 # The test number to run.
server_options=$4 # The options to start the server with (i.e.: "-o option1,options2,...")
expected_exit_code=$5 # The expected exit code of ssh-audit.py.
server_exec="/openssh/sshd-${version} -D -f /etc/ssh/sshd_config-8.0p1_test1 ${server_options}"
test_result_stdout="${TEST_RESULT_DIR}/openssh_${version}_builtin_policy_${test_number}.txt"
test_result_json="${TEST_RESULT_DIR}/openssh_${version}_builtin_policy_${test_number}.json"
expected_result_stdout="test/docker/expected_results/openssh_${version}_builtin_policy_${test_number}.txt"
expected_result_json="test/docker/expected_results/openssh_${version}_builtin_policy_${test_number}.json"
test_name="OpenSSH ${version} built-in policy ${test_number}"
run_policy_test "${test_name}" "${server_exec}" "${policy_name}" "${test_result_stdout}" "${test_result_json}" "${expected_exit_code}"
}
function run_custom_policy_test {
config_number=$1 # The configuration number to use.
test_number=$2 # The policy test number to run.
expected_exit_code=$3 # The expected exit code of ssh-audit.py.
version=
config=
if [[ ${config_number} == 'config1' ]]; then
version='5.6p1'
config='sshd_config-5.6p1_test1'
elif [[ ${config_number} == 'config2' ]]; then
version='8.0p1'
config='sshd_config-8.0p1_test1'
elif [[ ${config_number} == 'config3' ]]; then
version='5.6p1'
config='sshd_config-5.6p1_test4'
fi
server_exec="/openssh/sshd-${version} -D -f /etc/ssh/${config}"
policy_path="test/docker/policies/policy_${test_number}.txt"
test_result_stdout="${TEST_RESULT_DIR}/openssh_${version}_custom_policy_${test_number}.txt"
test_result_json="${TEST_RESULT_DIR}/openssh_${version}_custom_policy_${test_number}.json"
expected_result_stdout="test/docker/expected_results/openssh_${version}_custom_policy_${test_number}.txt"
expected_result_json="test/docker/expected_results/openssh_${version}_custom_policy_${test_number}.json"
test_name="OpenSSH ${version} custom policy ${test_number}"
run_policy_test "${test_name}" "${server_exec}" "${policy_path}" "${test_result_stdout}" "${test_result_json}" "${expected_exit_code}"
}
function run_policy_test {
test_name=$1
server_exec=$2
policy_path=$3
test_result_stdout=$4
test_result_json=$5
expected_exit_code=$6
#echo "Running: docker run -d -p 2222:22 ${IMAGE_NAME}:${IMAGE_VERSION} ${server_exec}"
cid=`docker run -d -p 2222:22 ${IMAGE_NAME}:${IMAGE_VERSION} ${server_exec}`
if [[ $? != 0 ]]; then
echo -e "${REDB}Failed to run docker image! (exit code: $?)${CLR}"
exit 1
fi
#echo "Running: ./ssh-audit.py -P \"${policy_path}\" localhost:2222 > ${test_result_stdout}"
./ssh-audit.py -P "${policy_path}" localhost:2222 > ${test_result_stdout}
actual_exit_code=$?
if [[ ${actual_exit_code} != ${expected_exit_code} ]]; then
echo -e "${test_name} ${REDB}FAILED${CLR} (expected exit code: ${expected_exit_code}; actual exit code: ${actual_exit_code}\n"
cat ${test_result_stdout}
docker container stop -t 0 $cid > /dev/null
exit 1
fi
#echo "Running: ./ssh-audit.py -P \"${policy_path}\" -j localhost:2222 > ${test_result_json}"
./ssh-audit.py -P "${policy_path}" -j localhost:2222 > ${test_result_json}
actual_exit_code=$?
if [[ ${actual_exit_code} != ${expected_exit_code} ]]; then
echo -e "${test_name} ${REDB}FAILED${CLR} (expected exit code: ${expected_exit_code}; actual exit code: ${actual_exit_code}\n"
cat ${test_result_json}
docker container stop -t 0 $cid > /dev/null
exit 1
fi
docker container stop -t 0 $cid > /dev/null
if [[ $? != 0 ]]; then
echo -e "${REDB}Failed to stop docker container ${cid}! (exit code: $?)${CLR}"
exit 1
fi
diff=`diff -u ${expected_result_stdout} ${test_result_stdout}`
if [[ $? != 0 ]]; then
echo -e "${test_name} ${REDB}FAILED${CLR}.\n\n${diff}\n"
exit 1
fi
diff=`diff -u ${expected_result_json} ${test_result_json}`
if [[ $? != 0 ]]; then
echo -e "${test_name} ${REDB}FAILED${CLR}.\n\n${diff}\n"
exit 1
fi
echo -e "${test_name} ${GREEN}passed${CLR}."
}
# First check if docker is functional.
docker version > /dev/null
if [[ $? != 0 ]]; then
echo -e "${REDB}Error: 'docker version' command failed (error code: $?). Is docker installed and functioning?${CLR}"
exit 1
fi
# Check if the docker image is the most up-to-date version.
docker_image_exists=0
check_if_docker_image_exists
if [[ $? == 0 ]]; then
docker_image_exists=1
fi
# Check if the user specified --create to build a new image.
if [[ ($# == 1) && ($1 == "--create") ]]; then
# Ensure that the image name doesn't already exist before building.
if [[ $docker_image_exists == 1 ]]; then
echo -e "${REDB}Error: --create specified, but $IMAGE_NAME:$IMAGE_VERSION already exists!${CLR}"
exit 1
else
echo -e "\nCreating docker image $IMAGE_NAME:$IMAGE_VERSION..."
create_docker_image
echo -e "\n${GREEN}Done creating docker image!${CLR}"
exit 0
fi
fi
# If we weren't explicitly told to create a new image, and it doesn't exist, then pull it from Dockerhub.
if [[ $docker_image_exists == 0 ]]; then
echo -e "\nPulling docker image $IMAGE_NAME:$IMAGE_VERSION..."
pull_docker_image
fi
echo -e "\n${GREEN}Starting tests...${CLR}"
# Create a temporary directory to write test results to.
TEST_RESULT_DIR=`mktemp -d /tmp/ssh-audit_test-results_XXXXXXXXXX`
# Now run all the tests.
echo -e "\nRunning tests..."
run_openssh_test '4.0p1' 'test1' $PROGRAM_RETVAL_FAILURE
echo
run_openssh_test '5.6p1' 'test1' $PROGRAM_RETVAL_FAILURE
run_openssh_test '5.6p1' 'test2' $PROGRAM_RETVAL_FAILURE
run_openssh_test '5.6p1' 'test3' $PROGRAM_RETVAL_FAILURE
run_openssh_test '5.6p1' 'test4' $PROGRAM_RETVAL_FAILURE
run_openssh_test '5.6p1' 'test5' $PROGRAM_RETVAL_FAILURE
echo
run_openssh_test '8.0p1' 'test1' $PROGRAM_RETVAL_FAILURE
run_openssh_test '8.0p1' 'test2' $PROGRAM_RETVAL_FAILURE
run_openssh_test '8.0p1' 'test3' $PROGRAM_RETVAL_GOOD
echo
run_dropbear_test '2019.78' 'test1' '-r /etc/dropbear/dropbear_rsa_host_key_1024 -r /etc/dropbear/dropbear_dss_host_key -r /etc/dropbear/dropbear_ecdsa_host_key' 3
echo
run_tinyssh_test '20190101' 'test1' $PROGRAM_RETVAL_WARNING
echo
echo
run_custom_policy_test 'config1' 'test1' $PROGRAM_RETVAL_GOOD
run_custom_policy_test 'config1' 'test2' $PROGRAM_RETVAL_FAILURE
run_custom_policy_test 'config1' 'test3' $PROGRAM_RETVAL_FAILURE
run_custom_policy_test 'config1' 'test4' $PROGRAM_RETVAL_FAILURE
run_custom_policy_test 'config1' 'test5' $PROGRAM_RETVAL_FAILURE
run_custom_policy_test 'config2' 'test6' $PROGRAM_RETVAL_GOOD
# Passing test with host key certificate and CA key certificates.
run_custom_policy_test 'config3' 'test7' $PROGRAM_RETVAL_GOOD
# Failing test with host key certificate and non-compliant CA key length.
run_custom_policy_test 'config3' 'test8' $PROGRAM_RETVAL_FAILURE
# Failing test with non-compliant host key certificate and CA key certificate.
run_custom_policy_test 'config3' 'test9' $PROGRAM_RETVAL_FAILURE
# Failing test with non-compliant host key certificate and non-compliant CA key certificate.
run_custom_policy_test 'config3' 'test10' $PROGRAM_RETVAL_FAILURE
# Passing test with host key size check.
run_custom_policy_test 'config2' 'test11' $PROGRAM_RETVAL_GOOD
# Failing test with non-compliant host key size check.
run_custom_policy_test 'config2' 'test12' $PROGRAM_RETVAL_FAILURE
# Passing test with DH modulus test.
run_custom_policy_test 'config2' 'test13' $PROGRAM_RETVAL_GOOD
# Failing test with DH modulus test.
run_custom_policy_test 'config2' 'test14' $PROGRAM_RETVAL_FAILURE
# Passing test for built-in OpenSSH 8.0p1 server policy.
run_builtin_policy_test "Hardened OpenSSH Server v8.0 (version 1)" "8.0p1" "test1" "-o HostKeyAlgorithms=ssh-ed25519 -o KexAlgorithms=curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256 -o Ciphers=chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr -o MACs=hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com" $PROGRAM_RETVAL_GOOD
# Failing test for built-in OpenSSH 8.0p1 server policy (MACs not hardened).
run_builtin_policy_test "Hardened OpenSSH Server v8.0 (version 1)" "8.0p1" "test2" "-o HostKeyAlgorithms=ssh-ed25519 -o KexAlgorithms=curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256 -o Ciphers=chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr" $PROGRAM_RETVAL_FAILURE
if [[ $num_failures == 0 ]]; then
echo -e "\n${GREENB}ALL TESTS PASS!${CLR}\n"
rm -rf $TEST_RESULT_DIR
else
echo -e "\n${REDB}${num_failures} TESTS FAILED!${CLR}\n"
fi
exit 0

7
pyproject.toml Normal file
View File

@ -0,0 +1,7 @@
[build-system]
# https://pip.pypa.io/en/stable/reference/pip/#pep-517-and-518-support
requires = [
"setuptools>=40.8.0",
"wheel"
]
build-backend = "setuptools.build_meta"

41
setup.cfg Normal file
View File

@ -0,0 +1,41 @@
[metadata]
name = ssh-audit
version = attr: ssh_audit.globals.VERSION
author = Joe Testa
author_email = jtesta@positronsecurity.com
description = An SSH server & client configuration security auditing tool
long_description = file: README.md
long_description_content_type = text/markdown
license_file = LICENSE
url = https://github.com/jtesta/ssh-audit
project_urls =
Source Code = https://github.com/jtesta/ssh-audit
Bug Tracker = https://github.com/jtesta/ssh-audit/issues
classifiers =
Development Status :: 5 - Production/Stable
Intended Audience :: Information Technology
Intended Audience :: System Administrators
License :: OSI Approved :: MIT License
Operating System :: OS Independent
Programming Language :: Python :: 3
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy
Topic :: Security
Topic :: Security :: Cryptography
[options]
packages = find:
package_dir =
= src
python_requires = >=3.6,<4
[options.packages.find]
where = src
[options.entry_points]
console_scripts =
ssh-audit = ssh_audit.ssh_audit:main

23
setup.py Normal file
View File

@ -0,0 +1,23 @@
import re
import sys
from setuptools import setup
print_warning = False
m = re.search(r'^VERSION\s*=\s*\'v(\d\.\d\.\d)\'', open('src/ssh_audit/globals.py').read(), re.M)
if m is None:
# If we failed to parse the stable version, see if this is the development version.
m = re.search(r'^VERSION\s*=\s*\'v(\d\.\d\.\d-dev)\'', open('src/ssh_audit/globals.py').read(), re.M)
if m is None:
print("Error: could not parse VERSION variable from ssh_audit.py.")
sys.exit(1)
else: # Continue with the development version, but print a warning later.
print_warning = True
version = m.group(1)
print("\n\nPackaging ssh-audit v%s...\n\n" % version)
# see setup.cfg
setup()
if print_warning:
print("\n\n !!! WARNING: development version detected (%s). Are you sure you want to package this version? Probably not...\n" % version)

21
snapcraft.yaml Normal file
View File

@ -0,0 +1,21 @@
name: ssh-audit
version: '2.4.0-1'
license: 'MIT'
summary: ssh-audit
description: |
SSH server and client security configuration auditor. Official repository: <https://github.com/jtesta/ssh-audit>
base: core20
grade: stable
confinement: strict
apps:
ssh-audit:
command: bin/ssh-audit
plugs: [network,network-bind]
parts:
ssh-audit:
plugin: python
# python-version: python3
source: .

View File

16
src/ssh_audit/__main__.py Normal file
View File

@ -0,0 +1,16 @@
import sys
import traceback
from ssh_audit.ssh_audit import main
from ssh_audit import exitcodes
exit_code = exitcodes.GOOD
try:
exit_code = main()
except Exception:
exit_code = exitcodes.UNKNOWN_ERROR
print(traceback.format_exc())
sys.exit(exit_code)

View File

@ -0,0 +1,61 @@
"""
The MIT License (MIT)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
from ssh_audit.product import Product
class Algorithm:
@staticmethod
def get_ssh_version(version_desc: str) -> Tuple[str, str, bool]:
is_client = version_desc.endswith('C')
if is_client:
version_desc = version_desc[:-1]
if version_desc.startswith('d'):
return Product.DropbearSSH, version_desc[1:], is_client
elif version_desc.startswith('l1'):
return Product.LibSSH, version_desc[2:], is_client
else:
return Product.OpenSSH, version_desc, is_client
@classmethod
def get_since_text(cls, versions: List[Optional[str]]) -> Optional[str]:
tv = []
if len(versions) == 0 or versions[0] is None:
return None
for v in versions[0].split(','):
ssh_prod, ssh_ver, is_cli = cls.get_ssh_version(v)
if not ssh_ver:
continue
if ssh_prod in [Product.LibSSH]:
continue
if is_cli:
ssh_ver = '{} (client only)'.format(ssh_ver)
tv.append('{} {}'.format(ssh_prod, ssh_ver))
if len(tv) == 0:
return None
return 'available since ' + ', '.join(tv).rstrip(', ')

223
src/ssh_audit/algorithms.py Normal file
View File

@ -0,0 +1,223 @@
"""
The MIT License (MIT)
Copyright (C) 2017-2020 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
from ssh_audit.algorithm import Algorithm
from ssh_audit.product import Product
from ssh_audit.software import Software
from ssh_audit.ssh1_kexdb import SSH1_KexDB
from ssh_audit.ssh1_publickeymessage import SSH1_PublicKeyMessage
from ssh_audit.ssh2_kex import SSH2_Kex
from ssh_audit.ssh2_kexdb import SSH2_KexDB
from ssh_audit.timeframe import Timeframe
from ssh_audit.utils import Utils
class Algorithms:
def __init__(self, pkm: Optional[SSH1_PublicKeyMessage], kex: Optional[SSH2_Kex]) -> None:
self.__ssh1kex = pkm
self.__ssh2kex = kex
@property
def ssh1kex(self) -> Optional[SSH1_PublicKeyMessage]:
return self.__ssh1kex
@property
def ssh2kex(self) -> Optional[SSH2_Kex]:
return self.__ssh2kex
@property
def ssh1(self) -> Optional['Algorithms.Item']:
if self.ssh1kex is None:
return None
item = Algorithms.Item(1, SSH1_KexDB.ALGORITHMS)
item.add('key', [u'ssh-rsa1'])
item.add('enc', self.ssh1kex.supported_ciphers)
item.add('aut', self.ssh1kex.supported_authentications)
return item
@property
def ssh2(self) -> Optional['Algorithms.Item']:
if self.ssh2kex is None:
return None
item = Algorithms.Item(2, SSH2_KexDB.ALGORITHMS)
item.add('kex', self.ssh2kex.kex_algorithms)
item.add('key', self.ssh2kex.key_algorithms)
item.add('enc', self.ssh2kex.server.encryption)
item.add('mac', self.ssh2kex.server.mac)
return item
@property
def values(self) -> Iterable['Algorithms.Item']:
for item in [self.ssh1, self.ssh2]:
if item is not None:
yield item
@property
def maxlen(self) -> int:
def _ml(items: Sequence[str]) -> int:
return max(len(i) for i in items)
maxlen = 0
if self.ssh1kex is not None:
maxlen = max(_ml(self.ssh1kex.supported_ciphers),
_ml(self.ssh1kex.supported_authentications),
maxlen)
if self.ssh2kex is not None:
maxlen = max(_ml(self.ssh2kex.kex_algorithms),
_ml(self.ssh2kex.key_algorithms),
_ml(self.ssh2kex.server.encryption),
_ml(self.ssh2kex.server.mac),
maxlen)
return maxlen
def get_ssh_timeframe(self, for_server: Optional[bool] = None) -> 'Timeframe':
timeframe = Timeframe()
for alg_pair in self.values:
alg_db = alg_pair.db
for alg_type, alg_list in alg_pair.items():
for alg_name in alg_list:
alg_name_native = Utils.to_text(alg_name)
alg_desc = alg_db[alg_type].get(alg_name_native)
if alg_desc is None:
continue
versions = alg_desc[0]
timeframe.update(versions, for_server)
return timeframe
def get_recommendations(self, software: Optional['Software'], for_server: bool = True) -> Tuple[Optional['Software'], Dict[int, Dict[str, Dict[str, Dict[str, int]]]]]:
# pylint: disable=too-many-locals,too-many-statements
vproducts = [Product.OpenSSH,
Product.DropbearSSH,
Product.LibSSH,
Product.TinySSH]
# Set to True if server is not one of vproducts, above.
unknown_software = False
if software is not None:
if software.product not in vproducts:
unknown_software = True
# The code below is commented out because it would try to guess what the server is,
# usually resulting in wild & incorrect recommendations.
# if software is None:
# ssh_timeframe = self.get_ssh_timeframe(for_server)
# for product in vproducts:
# if product not in ssh_timeframe:
# continue
# version = ssh_timeframe.get_from(product, for_server)
# if version is not None:
# software = SSH.Software(None, product, version, None, None)
# break
rec: Dict[int, Dict[str, Dict[str, Dict[str, int]]]] = {}
if software is None:
unknown_software = True
for alg_pair in self.values:
sshv, alg_db = alg_pair.sshv, alg_pair.db
rec[sshv] = {}
for alg_type, alg_list in alg_pair.items():
if alg_type == 'aut':
continue
rec[sshv][alg_type] = {'add': {}, 'del': {}, 'chg': {}}
for n, alg_desc in alg_db[alg_type].items():
versions = alg_desc[0]
empty_version = False
if len(versions) == 0 or versions[0] is None:
empty_version = True
else:
matches = False
if unknown_software:
matches = True
for v in versions[0].split(','):
ssh_prefix, ssh_version, is_cli = Algorithm.get_ssh_version(v)
if not ssh_version:
continue
if (software is not None) and (ssh_prefix != software.product):
continue
if is_cli and for_server:
continue
if (software is not None) and (software.compare_version(ssh_version) < 0):
continue
matches = True
break
if not matches:
continue
adl, faults = len(alg_desc), 0
for i in range(1, 3):
if not adl > i:
continue
fc = len(alg_desc[i])
if fc > 0:
faults += pow(10, 2 - i) * fc
if n not in alg_list:
# Don't recommend certificate or token types; these will only appear in the server's list if they are fully configured & functional on the server.
if faults > 0 or (alg_type == 'key' and (('-cert-' in n) or (n.startswith('sk-')))) or empty_version:
continue
rec[sshv][alg_type]['add'][n] = 0
else:
if faults == 0:
continue
if n in ['diffie-hellman-group-exchange-sha256', 'rsa-sha2-256', 'rsa-sha2-512', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com']:
rec[sshv][alg_type]['chg'][n] = faults
else:
rec[sshv][alg_type]['del'][n] = faults
# If we are working with unknown software, drop all add recommendations, because we don't know if they're valid.
if unknown_software:
rec[sshv][alg_type]['add'] = {}
add_count = len(rec[sshv][alg_type]['add'])
del_count = len(rec[sshv][alg_type]['del'])
chg_count = len(rec[sshv][alg_type]['chg'])
if add_count == 0:
del rec[sshv][alg_type]['add']
if del_count == 0:
del rec[sshv][alg_type]['del']
if chg_count == 0:
del rec[sshv][alg_type]['chg']
if len(rec[sshv][alg_type]) == 0:
del rec[sshv][alg_type]
if len(rec[sshv]) == 0:
del rec[sshv]
return software, rec
class Item:
def __init__(self, sshv: int, db: Dict[str, Dict[str, List[List[Optional[str]]]]]) -> None:
self.__sshv = sshv
self.__db = db
self.__storage: Dict[str, List[str]] = {}
@property
def sshv(self) -> int:
return self.__sshv
@property
def db(self) -> Dict[str, Dict[str, List[List[Optional[str]]]]]:
return self.__db
def add(self, key: str, value: List[str]) -> None:
self.__storage[key] = value
def items(self) -> Iterable[Tuple[str, List[str]]]:
return self.__storage.items()

View File

@ -0,0 +1,96 @@
"""
The MIT License (MIT)
Copyright (C) 2017-2020 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
from ssh_audit.policy import Policy
from ssh_audit.utils import Utils
class AuditConf:
# pylint: disable=too-many-instance-attributes
def __init__(self, host: str = '', port: int = 22) -> None:
self.host = host
self.port = port
self.ssh1 = True
self.ssh2 = True
self.batch = False
self.client_audit = False
self.colors = True
self.json = False
self.verbose = False
self.level = 'info'
self.ip_version_preference: List[int] = [] # Holds only 5 possible values: [] (no preference), [4] (use IPv4 only), [6] (use IPv6 only), [46] (use both IPv4 and IPv6, but prioritize v4), and [64] (use both IPv4 and IPv6, but prioritize v6).
self.ipv4 = False
self.ipv6 = False
self.make_policy = False # When True, creates a policy file from an audit scan.
self.policy_file: Optional[str] = None # File system path to a policy
self.policy: Optional[Policy] = None # Policy object
self.timeout = 5.0
self.timeout_set = False # Set to True when the user explicitly sets it.
self.target_file: Optional[str] = None
self.target_list: List[str] = []
self.threads = 32
self.list_policies = False
self.lookup = ''
self.manual = False
def __setattr__(self, name: str, value: Union[str, int, float, bool, Sequence[int]]) -> None:
valid = False
if name in ['batch', 'client_audit', 'colors', 'json', 'list_policies', 'manual', 'make_policy', 'ssh1', 'ssh2', 'timeout_set', 'verbose']:
valid, value = True, bool(value)
elif name in ['ipv4', 'ipv6']:
valid, value = True, bool(value)
if len(self.ip_version_preference) == 2: # Being called more than twice is not valid.
valid = False
elif value:
self.ip_version_preference.append(4 if name == 'ipv4' else 6)
elif name == 'port':
valid, port = True, Utils.parse_int(value)
if port < 1 or port > 65535:
raise ValueError('invalid port: {}'.format(value))
value = port
elif name in ['level']:
if value not in ('info', 'warn', 'fail'):
raise ValueError('invalid level: {}'.format(value))
valid = True
elif name == 'host':
valid = True
elif name == 'timeout':
value = Utils.parse_float(value)
if value == -1.0:
raise ValueError('invalid timeout: {}'.format(value))
valid = True
elif name in ['ip_version_preference', 'lookup', 'policy_file', 'policy', 'target_file', 'target_list']:
valid = True
elif name == "threads":
valid, num_threads = True, Utils.parse_int(value)
if num_threads < 1:
raise ValueError('invalid number of threads: {}'.format(value))
value = num_threads
if valid:
object.__setattr__(self, name, value)

92
src/ssh_audit/banner.py Normal file
View File

@ -0,0 +1,92 @@
"""
The MIT License (MIT)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import re
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
from ssh_audit.utils import Utils
class Banner:
_RXP, _RXR = r'SSH-\d\.\s*?\d+', r'(-\s*([^\s]*)(?:\s+(.*))?)?'
RX_PROTOCOL = re.compile(re.sub(r'\\d(\+?)', r'(\\d\g<1>)', _RXP))
RX_BANNER = re.compile(r'^({0}(?:(?:-{0})*)){1}$'.format(_RXP, _RXR))
def __init__(self, protocol: Tuple[int, int], software: Optional[str], comments: Optional[str], valid_ascii: bool) -> None:
self.__protocol = protocol
self.__software = software
self.__comments = comments
self.__valid_ascii = valid_ascii
@property
def protocol(self) -> Tuple[int, int]:
return self.__protocol
@property
def software(self) -> Optional[str]:
return self.__software
@property
def comments(self) -> Optional[str]:
return self.__comments
@property
def valid_ascii(self) -> bool:
return self.__valid_ascii
def __str__(self) -> str:
r = 'SSH-{}.{}'.format(self.protocol[0], self.protocol[1])
if self.software is not None:
r += '-{}'.format(self.software)
if bool(self.comments):
r += ' {}'.format(self.comments)
return r
def __repr__(self) -> str:
p = '{}.{}'.format(self.protocol[0], self.protocol[1])
r = 'protocol={}'.format(p)
if self.software is not None:
r += ', software={}'.format(self.software)
if bool(self.comments):
r += ', comments={}'.format(self.comments)
return '<{}({})>'.format(self.__class__.__name__, r)
@classmethod
def parse(cls, banner: str) -> Optional['Banner']:
valid_ascii = Utils.is_print_ascii(banner)
ascii_banner = Utils.to_print_ascii(banner)
mx = cls.RX_BANNER.match(ascii_banner)
if mx is None:
return None
protocol = min(re.findall(cls.RX_PROTOCOL, mx.group(1)))
protocol = (int(protocol[0]), int(protocol[1]))
software = (mx.group(3) or '').strip() or None
if software is None and (mx.group(2) or '').startswith('-'):
software = ''
comments = (mx.group(4) or '').strip() or None
if comments is not None:
comments = re.sub(r'\s+', ' ', comments)
return cls(protocol, software, comments, valid_ascii)

View File

@ -0,0 +1,6 @@
# The program return values corresponding to failure(s) encountered, warning(s) encountered, connection errors, and no problems found, respectively.
FAILURE = 3
WARNING = 2
CONNECTION_ERROR = 1
GOOD = 0
UNKNOWN_ERROR = -1

View File

@ -0,0 +1,43 @@
"""
The MIT License (MIT)
Copyright (C) 2017-2020 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import base64
import hashlib
class Fingerprint:
def __init__(self, fpd: bytes) -> None:
self.__fpd = fpd
@property
def md5(self) -> str:
h = hashlib.md5(self.__fpd).hexdigest()
r = u':'.join(h[i:i + 2] for i in range(0, len(h), 2))
return u'MD5:{}'.format(r)
@property
def sha256(self) -> str:
h = base64.b64encode(hashlib.sha256(self.__fpd).digest())
r = h.decode('ascii').rstrip('=')
return u'SHA256:{}'.format(r)

150
src/ssh_audit/gextest.py Normal file
View File

@ -0,0 +1,150 @@
"""
The MIT License (MIT)
Copyright (C) 2017-2021 Joe Testa (jtesta@positronsecurity.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
from ssh_audit.kexdh import KexGroupExchange_SHA1, KexGroupExchange_SHA256
from ssh_audit.ssh2_kexdb import SSH2_KexDB
from ssh_audit.ssh2_kex import SSH2_Kex
from ssh_audit.ssh_socket import SSH_Socket
# Performs DH group exchanges to find what moduli are supported, and checks
# their size.
class GEXTest:
# Creates a new connection to the server. Returns True on success, or False.
@staticmethod
def reconnect(s: 'SSH_Socket', kex: 'SSH2_Kex', gex_alg: str) -> bool:
if s.is_connected():
return True
err = s.connect()
if err is not None:
return False
_, _, err = s.get_banner()
if err is not None:
s.close()
return False
# Send our KEX using the specified group-exchange and most of the
# server's own values.
s.send_kexinit(key_exchanges=[gex_alg], hostkeys=kex.key_algorithms, ciphers=kex.server.encryption, macs=kex.server.mac, compressions=kex.server.compression, languages=kex.server.languages)
# Parse the server's KEX.
_, payload = s.read_packet(2)
SSH2_Kex.parse(payload)
return True
# Runs the DH moduli test against the specified target.
@staticmethod
def run(s: 'SSH_Socket', kex: 'SSH2_Kex') -> None:
GEX_ALGS = {
'diffie-hellman-group-exchange-sha1': KexGroupExchange_SHA1,
'diffie-hellman-group-exchange-sha256': KexGroupExchange_SHA256,
}
# The previous RSA tests put the server in a state we can't
# test. So we need a new connection to start with a clean
# slate.
if s.is_connected():
s.close()
# Check if the server supports any of the group-exchange
# algorithms. If so, test each one.
for gex_alg in GEX_ALGS:
if gex_alg in kex.kex_algorithms:
if GEXTest.reconnect(s, kex, gex_alg) is False:
break
kex_group = GEX_ALGS[gex_alg]()
smallest_modulus = -1
# First try a range of weak sizes.
try:
kex_group.send_init_gex(s, 512, 1024, 1536)
kex_group.recv_reply(s, False)
# Its been observed that servers will return a group
# larger than the requested max. So just because we
# got here, doesn't mean the server is vulnerable...
smallest_modulus = kex_group.get_dh_modulus_size()
except Exception:
pass
finally:
s.close()
# Try an array of specific modulus sizes... one at a time.
reconnect_failed = False
for bits in [512, 768, 1024, 1536, 2048, 3072, 4096]:
# If we found one modulus size already, but we're about
# to test a larger one, don't bother.
if bits >= smallest_modulus > 0:
break
if GEXTest.reconnect(s, kex, gex_alg) is False:
reconnect_failed = True
break
try:
kex_group.send_init_gex(s, bits, bits, bits)
kex_group.recv_reply(s, False)
smallest_modulus = kex_group.get_dh_modulus_size()
except Exception:
# import traceback
# print(traceback.format_exc())
pass
finally:
# The server is in a state that is not re-testable,
# so there's nothing else to do with this open
# connection.
s.close()
if smallest_modulus > 0:
kex.set_dh_modulus_size(gex_alg, smallest_modulus)
# We flag moduli smaller than 2048 as a failure.
if smallest_modulus < 2048:
text = 'using small %d-bit modulus' % smallest_modulus
lst = SSH2_KexDB.ALGORITHMS['kex'][gex_alg]
# For 'diffie-hellman-group-exchange-sha256', add
# a failure reason.
if len(lst) == 1:
lst.append([text])
# For 'diffie-hellman-group-exchange-sha1', delete
# the existing failure reason (which is vague), and
# insert our own.
else:
del lst[1]
lst.insert(1, [text])
if reconnect_failed:
break

27
src/ssh_audit/globals.py Normal file
View File

@ -0,0 +1,27 @@
"""
The MIT License (MIT)
Copyright (C) 2017-2021 Joe Testa (jtesta@positronsecurity.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
VERSION = 'v2.4.0'
SSH_HEADER = 'SSH-{0}-OpenSSH_8.2' # SSH software to impersonate
GITHUB_ISSUES_URL = 'https://github.com/jtesta/ssh-audit/issues' # The URL to the Github issues tracker.
WINDOWS_MAN_PAGE = ''

View File

@ -0,0 +1,178 @@
"""
The MIT License (MIT)
Copyright (C) 2017-2021 Joe Testa (jtesta@positronsecurity.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
from ssh_audit.kexdh import KexDH, KexGroup1, KexGroup14_SHA1, KexGroup14_SHA256, KexCurve25519_SHA256, KexGroup16_SHA512, KexGroup18_SHA512, KexGroupExchange_SHA1, KexGroupExchange_SHA256, KexNISTP256, KexNISTP384, KexNISTP521
from ssh_audit.ssh2_kex import SSH2_Kex
from ssh_audit.ssh2_kexdb import SSH2_KexDB
from ssh_audit.ssh_socket import SSH_Socket
# Obtains host keys, checks their size, and derives their fingerprints.
class HostKeyTest:
# Tracks the RSA host key types. As of this writing, testing one in this family yields valid results for the rest.
RSA_FAMILY = ['ssh-rsa', 'rsa-sha2-256', 'rsa-sha2-512']
# Dict holding the host key types we should extract & parse. 'cert' is True to denote that a host key type handles certificates (thus requires additional parsing). 'variable_key_len' is True for host key types that can have variable sizes (True only for RSA types, as the rest are of fixed-size). After the host key type is fully parsed, the key 'parsed' is added with a value of True.
HOST_KEY_TYPES = {
'ssh-rsa': {'cert': False, 'variable_key_len': True},
'rsa-sha2-256': {'cert': False, 'variable_key_len': True},
'rsa-sha2-512': {'cert': False, 'variable_key_len': True},
'ssh-rsa-cert-v01@openssh.com': {'cert': True, 'variable_key_len': True},
'rsa-sha2-256-cert-v01@openssh.com': {'cert': True, 'variable_key_len': True},
'rsa-sha2-512-cert-v01@openssh.com': {'cert': True, 'variable_key_len': True},
'ssh-ed25519': {'cert': False, 'variable_key_len': False},
'ssh-ed25519-cert-v01@openssh.com': {'cert': True, 'variable_key_len': False},
}
@staticmethod
def run(s: 'SSH_Socket', server_kex: 'SSH2_Kex') -> None:
KEX_TO_DHGROUP = {
'diffie-hellman-group1-sha1': KexGroup1,
'diffie-hellman-group14-sha1': KexGroup14_SHA1,
'diffie-hellman-group14-sha256': KexGroup14_SHA256,
'curve25519-sha256': KexCurve25519_SHA256,
'curve25519-sha256@libssh.org': KexCurve25519_SHA256,
'diffie-hellman-group16-sha512': KexGroup16_SHA512,
'diffie-hellman-group18-sha512': KexGroup18_SHA512,
'diffie-hellman-group-exchange-sha1': KexGroupExchange_SHA1,
'diffie-hellman-group-exchange-sha256': KexGroupExchange_SHA256,
'ecdh-sha2-nistp256': KexNISTP256,
'ecdh-sha2-nistp384': KexNISTP384,
'ecdh-sha2-nistp521': KexNISTP521,
# 'kexguess2@matt.ucc.asn.au': ???
}
# Pick the first kex algorithm that the server supports, which we
# happen to support as well.
kex_str = None
kex_group = None
for server_kex_alg in server_kex.kex_algorithms:
if server_kex_alg in KEX_TO_DHGROUP:
kex_str = server_kex_alg
kex_group = KEX_TO_DHGROUP[kex_str]()
break
if kex_str is not None and kex_group is not None:
HostKeyTest.perform_test(s, server_kex, kex_str, kex_group, HostKeyTest.HOST_KEY_TYPES)
@staticmethod
def perform_test(s: 'SSH_Socket', server_kex: 'SSH2_Kex', kex_str: str, kex_group: 'KexDH', host_key_types: Dict[str, Dict[str, bool]]) -> None:
hostkey_modulus_size = 0
ca_modulus_size = 0
# If the connection still exists, close it so we can test
# using a clean slate (otherwise it may exist in a non-testable
# state).
if s.is_connected():
s.close()
# For each host key type...
for host_key_type in host_key_types:
# Skip those already handled (i.e.: those in the RSA family, as testing one tests them all).
if 'parsed' in host_key_types[host_key_type] and host_key_types[host_key_type]['parsed']:
continue
# If this host key type is supported by the server, we test it.
if host_key_type in server_kex.key_algorithms:
cert = host_key_types[host_key_type]['cert']
variable_key_len = host_key_types[host_key_type]['variable_key_len']
# If the connection is closed, re-open it and get the kex again.
if not s.is_connected():
err = s.connect()
if err is not None:
return
_, _, err = s.get_banner()
if err is not None:
s.close()
return
# Send our KEX using the specified group-exchange and most of the server's own values.
s.send_kexinit(key_exchanges=[kex_str], hostkeys=[host_key_type], ciphers=server_kex.server.encryption, macs=server_kex.server.mac, compressions=server_kex.server.compression, languages=server_kex.server.languages)
# Parse the server's KEX.
_, payload = s.read_packet()
SSH2_Kex.parse(payload)
# Do the initial DH exchange. The server responds back
# with the host key and its length. Bingo. We also get back the host key fingerprint.
kex_group.send_init(s)
try:
host_key = kex_group.recv_reply(s, variable_key_len)
if host_key is not None:
server_kex.set_host_key(host_key_type, host_key)
except Exception:
pass
hostkey_modulus_size = kex_group.get_hostkey_size()
ca_modulus_size = kex_group.get_ca_size()
# Close the socket, as the connection has
# been put in a state that later tests can't use.
s.close()
# If the host key modulus or CA modulus was successfully parsed, check to see that its a safe size.
if hostkey_modulus_size > 0 or ca_modulus_size > 0:
# Set the hostkey size for all RSA key types since 'ssh-rsa',
# 'rsa-sha2-256', etc. are all using the same host key.
# Note, however, that this may change in the future.
if cert is False and host_key_type in HostKeyTest.RSA_FAMILY:
for rsa_type in HostKeyTest.RSA_FAMILY:
server_kex.set_rsa_key_size(rsa_type, hostkey_modulus_size)
elif cert is True:
server_kex.set_rsa_key_size(host_key_type, hostkey_modulus_size, ca_modulus_size)
# Keys smaller than 2048 result in a failure. Update the database accordingly.
if (cert is False) and (hostkey_modulus_size < 2048):
for rsa_type in HostKeyTest.RSA_FAMILY:
alg_list = SSH2_KexDB.ALGORITHMS['key'][rsa_type]
# If no failure list exists, add an empty failure list.
if len(alg_list) < 2:
alg_list.append([])
alg_list[1].append('using small %d-bit modulus' % hostkey_modulus_size)
elif (cert is True) and ((hostkey_modulus_size < 2048) or (ca_modulus_size > 0 and ca_modulus_size < 2048)): # pylint: disable=chained-comparison
alg_list = SSH2_KexDB.ALGORITHMS['key'][host_key_type]
min_modulus = min(hostkey_modulus_size, ca_modulus_size)
min_modulus = min_modulus if min_modulus > 0 else max(hostkey_modulus_size, ca_modulus_size)
# If no failure list exists, add an empty failure list.
if len(alg_list) < 2:
alg_list.append([])
alg_list[1].append('using small %d-bit modulus' % min_modulus)
# If this host key type is in the RSA family, then mark them all as parsed (since results in one are valid for them all).
if host_key_type in HostKeyTest.RSA_FAMILY:
for rsa_type in HostKeyTest.RSA_FAMILY:
host_key_types[rsa_type]['parsed'] = True
else:
host_key_types[host_key_type]['parsed'] = True

365
src/ssh_audit/kexdh.py Normal file
View File

@ -0,0 +1,365 @@
"""
The MIT License (MIT)
Copyright (C) 2017-2020 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import binascii
import os
import random
import struct
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
from ssh_audit.protocol import Protocol
from ssh_audit.ssh_socket import SSH_Socket
class KexDH: # pragma: nocover
def __init__(self, kex_name: str, hash_alg: str, g: int, p: int) -> None:
self.__kex_name = kex_name
self.__hash_alg = hash_alg
self.__g = 0
self.__p = 0
self.__q = 0
self.__x = 0
self.__e = 0
self.set_params(g, p)
self.__ed25519_pubkey: Optional[bytes] = None
self.__hostkey_type: Optional[bytes] = None
self.__hostkey_e = 0
self.__hostkey_n = 0
self.__hostkey_n_len = 0 # Length of the host key modulus.
self.__ca_n_len = 0 # Length of the CA key modulus (if hostkey is a cert).
def set_params(self, g: int, p: int) -> None:
self.__g = g
self.__p = p
self.__q = (self.__p - 1) // 2
self.__x = 0
self.__e = 0
def send_init(self, s: SSH_Socket, init_msg: int = Protocol.MSG_KEXDH_INIT) -> None:
r = random.SystemRandom()
self.__x = r.randrange(2, self.__q)
self.__e = pow(self.__g, self.__x, self.__p)
s.write_byte(init_msg)
s.write_mpint2(self.__e)
s.send_packet()
# Parse a KEXDH_REPLY or KEXDH_GEX_REPLY message from the server. This
# contains the host key, among other things. Function returns the host
# key blob (from which the fingerprint can be calculated).
def recv_reply(self, s: 'SSH_Socket', parse_host_key_size: bool = True) -> Optional[bytes]:
packet_type, payload = s.read_packet(2)
# Skip any & all MSG_DEBUG messages.
while packet_type == Protocol.MSG_DEBUG:
packet_type, payload = s.read_packet(2)
if packet_type != -1 and packet_type not in [Protocol.MSG_KEXDH_REPLY, Protocol.MSG_KEXDH_GEX_REPLY]: # pylint: disable=no-else-raise
# TODO: change Exception to something more specific.
raise Exception('Expected MSG_KEXDH_REPLY (%d) or MSG_KEXDH_GEX_REPLY (%d), but got %d instead.' % (Protocol.MSG_KEXDH_REPLY, Protocol.MSG_KEXDH_GEX_REPLY, packet_type))
elif packet_type == -1:
# A connection error occurred. We can't parse anything, so just
# return. The host key modulus (and perhaps certificate modulus)
# will remain at length 0.
return None
hostkey_len = 0 # pylint: disable=unused-variable
hostkey_type_len = hostkey_e_len = 0 # pylint: disable=unused-variable
key_id_len = principles_len = 0 # pylint: disable=unused-variable
critical_options_len = extensions_len = 0 # pylint: disable=unused-variable
nonce_len = ca_key_len = ca_key_type_len = 0 # pylint: disable=unused-variable
ca_key_len = ca_key_type_len = ca_key_e_len = 0 # pylint: disable=unused-variable
key_id = principles = None # pylint: disable=unused-variable
critical_options = extensions = None # pylint: disable=unused-variable
nonce = ca_key = ca_key_type = None # pylint: disable=unused-variable
ca_key_e = ca_key_n = None # pylint: disable=unused-variable
# Get the host key blob, F, and signature.
ptr = 0
hostkey, hostkey_len, ptr = KexDH.__get_bytes(payload, ptr)
# If we are not supposed to parse the host key size (i.e.: it is a type that is of fixed size such as ed25519), then stop here.
if not parse_host_key_size:
return hostkey
_, _, ptr = KexDH.__get_bytes(payload, ptr)
_, _, ptr = KexDH.__get_bytes(payload, ptr)
# Now pick apart the host key blob.
# Get the host key type (i.e.: 'ssh-rsa', 'ssh-ed25519', etc).
ptr = 0
self.__hostkey_type, hostkey_type_len, ptr = KexDH.__get_bytes(hostkey, ptr)
# If this is an RSA certificate, skip over the nonce.
if self.__hostkey_type.startswith(b'ssh-rsa-cert-v0'):
nonce, nonce_len, ptr = KexDH.__get_bytes(hostkey, ptr)
# The public key exponent.
hostkey_e, hostkey_e_len, ptr = KexDH.__get_bytes(hostkey, ptr)
self.__hostkey_e = int(binascii.hexlify(hostkey_e), 16)
# Here is the modulus size & actual modulus of the host key public key.
hostkey_n, self.__hostkey_n_len, ptr = KexDH.__get_bytes(hostkey, ptr)
self.__hostkey_n = int(binascii.hexlify(hostkey_n), 16)
# If this is an RSA certificate, continue parsing to extract the CA
# key.
if self.__hostkey_type.startswith(b'ssh-rsa-cert-v0'):
# Skip over the serial number.
ptr += 8
# Get the certificate type.
cert_type = int(binascii.hexlify(hostkey[ptr:ptr + 4]), 16)
ptr += 4
# Only SSH2_CERT_TYPE_HOST (2) makes sense in this context.
if cert_type == 2:
# Skip the key ID (this is the serial number of the
# certificate).
key_id, key_id_len, ptr = KexDH.__get_bytes(hostkey, ptr)
# The principles, which are... I don't know what.
principles, principles_len, ptr = KexDH.__get_bytes(hostkey, ptr)
# Skip over the timestamp that this certificate is valid after.
ptr += 8
# Skip over the timestamp that this certificate is valid before.
ptr += 8
# TODO: validate the principles, and time range.
# The critical options.
critical_options, critical_options_len, ptr = KexDH.__get_bytes(hostkey, ptr)
# Certificate extensions.
extensions, extensions_len, ptr = KexDH.__get_bytes(hostkey, ptr)
# Another nonce.
nonce, nonce_len, ptr = KexDH.__get_bytes(hostkey, ptr)
# Finally, we get to the CA key.
ca_key, ca_key_len, ptr = KexDH.__get_bytes(hostkey, ptr)
# Last in the host key blob is the CA signature. It isn't
# interesting to us, so we won't bother parsing any further.
# The CA key has the modulus, however...
ptr = 0
# 'ssh-rsa', 'rsa-sha2-256', etc.
ca_key_type, ca_key_type_len, ptr = KexDH.__get_bytes(ca_key, ptr)
# CA's public key exponent.
ca_key_e, ca_key_e_len, ptr = KexDH.__get_bytes(ca_key, ptr)
# CA's modulus. Bingo.
ca_key_n, self.__ca_n_len, ptr = KexDH.__get_bytes(ca_key, ptr)
return hostkey
@staticmethod
def __get_bytes(buf: bytes, ptr: int) -> Tuple[bytes, int, int]:
num_bytes = struct.unpack('>I', buf[ptr:ptr + 4])[0]
ptr += 4
return buf[ptr:ptr + num_bytes], num_bytes, ptr + num_bytes
# Converts a modulus length in bytes to its size in bits, after some
# possible adjustments.
@staticmethod
def __adjust_key_size(size: int) -> int:
size = size * 8
# Actual keys are observed to be about 8 bits bigger than expected
# (i.e.: 1024-bit keys have a 1032-bit modulus). Check if this is
# the case, and subtract 8 if so. This simply improves readability
# in the UI.
if (size >> 3) % 2 != 0:
size = size - 8
return size
# Returns the size of the hostkey, in bits.
def get_hostkey_size(self) -> int:
return KexDH.__adjust_key_size(self.__hostkey_n_len)
# Returns the size of the CA key, in bits.
def get_ca_size(self) -> int:
return KexDH.__adjust_key_size(self.__ca_n_len)
# Returns the size of the DH modulus, in bits.
def get_dh_modulus_size(self) -> int:
# -2 to account for the '0b' prefix in the string.
return len(bin(self.__p)) - 2
class KexGroup1(KexDH): # pragma: nocover
def __init__(self) -> None:
# rfc2409: second oakley group
p = int('ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece65381ffffffffffffffff', 16)
super(KexGroup1, self).__init__('KexGroup1', 'sha1', 2, p)
class KexGroup14(KexDH): # pragma: nocover
def __init__(self, hash_alg: str) -> None:
# rfc3526: 2048-bit modp group
p = int('ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aacaa68ffffffffffffffff', 16)
super(KexGroup14, self).__init__('KexGroup14', hash_alg, 2, p)
class KexGroup14_SHA1(KexGroup14):
def __init__(self) -> None:
super(KexGroup14_SHA1, self).__init__('sha1')
class KexGroup14_SHA256(KexGroup14):
def __init__(self) -> None:
super(KexGroup14_SHA256, self).__init__('sha256')
class KexGroup16_SHA512(KexDH):
def __init__(self) -> None:
# rfc3526: 4096-bit modp group
p = int('ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a92108011a723c12a787e6d788719a10bdba5b2699c327186af4e23c1a946834b6150bda2583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa6287c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1f612970cee2d7afb81bdd762170481cd0069127d5b05aa993b4ea988d8fddc186ffb7dc90a6c08f4df435c934063199ffffffffffffffff', 16)
super(KexGroup16_SHA512, self).__init__('KexGroup16_SHA512', 'sha512', 2, p)
class KexGroup18_SHA512(KexDH):
def __init__(self) -> None:
# rfc3526: 8192-bit modp group
p = int('ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a92108011a723c12a787e6d788719a10bdba5b2699c327186af4e23c1a946834b6150bda2583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa6287c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1f612970cee2d7afb81bdd762170481cd0069127d5b05aa993b4ea988d8fddc186ffb7dc90a6c08f4df435c93402849236c3fab4d27c7026c1d4dcb2602646dec9751e763dba37bdf8ff9406ad9e530ee5db382f413001aeb06a53ed9027d831179727b0865a8918da3edbebcf9b14ed44ce6cbaced4bb1bdb7f1447e6cc254b332051512bd7af426fb8f401378cd2bf5983ca01c64b92ecf032ea15d1721d03f482d7ce6e74fef6d55e702f46980c82b5a84031900b1c9e59e7c97fbec7e8f323a97a7e36cc88be0f1d45b7ff585ac54bd407b22b4154aacc8f6d7ebf48e1d814cc5ed20f8037e0a79715eef29be32806a1d58bb7c5da76f550aa3d8a1fbff0eb19ccb1a313d55cda56c9ec2ef29632387fe8d76e3c0468043e8f663f4860ee12bf2d5b0b7474d6e694f91e6dbe115974a3926f12fee5e438777cb6a932df8cd8bec4d073b931ba3bc832b68d9dd300741fa7bf8afc47ed2576f6936ba424663aab639c5ae4f5683423b4742bf1c978238f16cbe39d652de3fdb8befc848ad922222e04a4037c0713eb57a81a23f0c73473fc646cea306b4bcbc8862f8385ddfa9d4b7fa2c087e879683303ed5bdd3a062b3cf5b3a278a66d2a13f83f44f82ddf310ee074ab6a364597e899a0255dc164f31cc50846851df9ab48195ded7ea1b1d510bd7ee74d73faf36bc31ecfa268359046f4eb879f924009438b481c6cd7889a002ed5ee382bc9190da6fc026e479558e4475677e9aa9e3050e2765694dfc81f56e880b96e7160c980dd98edd3dfffffffffffffffff', 16)
super(KexGroup18_SHA512, self).__init__('KexGroup18_SHA512', 'sha512', 2, p)
class KexCurve25519_SHA256(KexDH):
def __init__(self) -> None:
super(KexCurve25519_SHA256, self).__init__('KexCurve25519_SHA256', 'sha256', 0, 0)
# To start an ED25519 kex, we simply send a random 256-bit number as the
# public key.
def send_init(self, s: 'SSH_Socket', init_msg: int = Protocol.MSG_KEXDH_INIT) -> None:
self.__ed25519_pubkey = os.urandom(32)
s.write_byte(init_msg)
s.write_string(self.__ed25519_pubkey)
s.send_packet()
class KexNISTP256(KexDH):
def __init__(self) -> None:
super(KexNISTP256, self).__init__('KexNISTP256', 'sha256', 0, 0)
# Because the server checks that the value sent here is valid (i.e.: it lies
# on the curve, among other things), we would have to write a lot of code
# or import an elliptic curve library in order to randomly generate a
# valid elliptic point each time. Hence, we will simply send a static
# value, which is enough for us to extract the server's host key.
def send_init(self, s: 'SSH_Socket', init_msg: int = Protocol.MSG_KEXDH_INIT) -> None:
s.write_byte(init_msg)
s.write_string(b'\x04\x0b\x60\x44\x9f\x8a\x11\x9e\xc7\x81\x0c\xa9\x98\xfc\xb7\x90\xaa\x6b\x26\x8c\x12\x4a\xc0\x09\xbb\xdf\xc4\x2c\x4c\x2c\x99\xb6\xe1\x71\xa0\xd4\xb3\x62\x47\x74\xb3\x39\x0c\xf2\x88\x4a\x84\x6b\x3b\x15\x77\xa5\x77\xd2\xa9\xc9\x94\xf9\xd5\x66\x19\xcd\x02\x34\xd1')
s.send_packet()
class KexNISTP384(KexDH):
def __init__(self) -> None:
super(KexNISTP384, self).__init__('KexNISTP384', 'sha256', 0, 0)
# See comment for KexNISTP256.send_init().
def send_init(self, s: 'SSH_Socket', init_msg: int = Protocol.MSG_KEXDH_INIT) -> None:
s.write_byte(init_msg)
s.write_string(b'\x04\xe2\x9b\x84\xce\xa1\x39\x50\xfe\x1e\xa3\x18\x70\x1c\xe2\x7a\xe4\xb5\x6f\xdf\x93\x9f\xd4\xf4\x08\xcc\x9b\x02\x10\xa4\xca\x77\x9c\x2e\x51\x44\x1d\x50\x7a\x65\x4e\x7e\x2f\x10\x2d\x2d\x4a\x32\xc9\x8e\x18\x75\x90\x6c\x19\x10\xda\xcc\xa8\xe9\xf4\xc4\x3a\x53\x80\x35\xf4\x97\x9c\x04\x16\xf9\x5a\xdc\xcc\x05\x94\x29\xfa\xc4\xd6\x87\x4e\x13\x21\xdb\x3d\x12\xac\xbd\x20\x3b\x60\xff\xe6\x58\x42')
s.send_packet()
class KexNISTP521(KexDH):
def __init__(self) -> None:
super(KexNISTP521, self).__init__('KexNISTP521', 'sha256', 0, 0)
# See comment for KexNISTP256.send_init().
def send_init(self, s: 'SSH_Socket', init_msg: int = Protocol.MSG_KEXDH_INIT) -> None:
s.write_byte(init_msg)
s.write_string(b'\x04\x01\x02\x90\x29\xe9\x8f\xa8\x04\xaf\x1c\x00\xf9\xc6\x29\xc0\x39\x74\x8e\xea\x47\x7e\x7c\xf7\x15\x6e\x43\x3b\x59\x13\x53\x43\xb0\xae\x0b\xe7\xe6\x7c\x55\x73\x52\xa5\x2a\xc1\x42\xde\xfc\xf4\x1f\x8b\x5a\x8d\xfa\xcd\x0a\x65\x77\xa8\xce\x68\xd2\xc6\x26\xb5\x3f\xee\x4b\x01\x7b\xd2\x96\x23\x69\x53\xc7\x01\xe1\x0d\x39\xe9\x87\x49\x3b\xc8\xec\xda\x0c\xf9\xca\xad\x89\x42\x36\x6f\x93\x78\x78\x31\x55\x51\x09\x51\xc0\x96\xd7\xea\x61\xbf\xc2\x44\x08\x80\x43\xed\xc6\xbb\xfb\x94\xbd\xf8\xdf\x2b\xd8\x0b\x2e\x29\x1b\x8c\xc4\x8a\x04\x2d\x3a')
s.send_packet()
class KexGroupExchange(KexDH):
def __init__(self, classname: str, hash_alg: str) -> None:
super(KexGroupExchange, self).__init__(classname, hash_alg, 0, 0)
def send_init(self, s: 'SSH_Socket', init_msg: int = Protocol.MSG_KEXDH_GEX_REQUEST) -> None:
self.send_init_gex(s)
# The group exchange starts with sending a message to the server with
# the minimum, maximum, and preferred number of bits are for the DH group.
# The server responds with a generator and prime modulus that matches that,
# then the handshake continues on like a normal DH handshake (except the
# SSH message types differ).
def send_init_gex(self, s: 'SSH_Socket', minbits: int = 1024, prefbits: int = 2048, maxbits: int = 8192) -> None:
# Send the initial group exchange request. Tell the server what range
# of modulus sizes we will accept, along with our preference.
s.write_byte(Protocol.MSG_KEXDH_GEX_REQUEST)
s.write_int(minbits)
s.write_int(prefbits)
s.write_int(maxbits)
s.send_packet()
packet_type, payload = s.read_packet(2)
if (packet_type != Protocol.MSG_KEXDH_GEX_GROUP) and (packet_type != Protocol.MSG_DEBUG): # pylint: disable=consider-using-in
# TODO: replace with a better exception type.
raise Exception('Expected MSG_KEXDH_GEX_REPLY (%d), but got %d instead.' % (Protocol.MSG_KEXDH_GEX_REPLY, packet_type))
# Skip any & all MSG_DEBUG messages.
while packet_type == Protocol.MSG_DEBUG:
packet_type, payload = s.read_packet(2)
# Parse the modulus (p) and generator (g) values from the server.
ptr = 0
p_len = struct.unpack('>I', payload[ptr:ptr + 4])[0]
ptr += 4
p = int(binascii.hexlify(payload[ptr:ptr + p_len]), 16)
ptr += p_len
g_len = struct.unpack('>I', payload[ptr:ptr + 4])[0]
ptr += 4
g = int(binascii.hexlify(payload[ptr:ptr + g_len]), 16)
ptr += g_len
# Now that we got the generator and modulus, perform the DH exchange
# like usual.
super(KexGroupExchange, self).set_params(g, p)
super(KexGroupExchange, self).send_init(s, Protocol.MSG_KEXDH_GEX_INIT)
class KexGroupExchange_SHA1(KexGroupExchange):
def __init__(self) -> None:
super(KexGroupExchange_SHA1, self).__init__('KexGroupExchange_SHA1', 'sha1')
class KexGroupExchange_SHA256(KexGroupExchange):
def __init__(self) -> None:
super(KexGroupExchange_SHA256, self).__init__('KexGroupExchange_SHA256', 'sha256')

View File

@ -0,0 +1,175 @@
"""
The MIT License (MIT)
Copyright (C) 2021 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import os
import sys
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
from ssh_audit.utils import Utils
class OutputBuffer:
LEVELS: Sequence[str] = ('info', 'warn', 'fail')
COLORS = {'head': 36, 'good': 32, 'warn': 33, 'fail': 31}
# Use brighter colors on Windows for better readability.
if Utils.is_windows():
COLORS = {'head': 96, 'good': 92, 'warn': 93, 'fail': 91}
def __init__(self, buffer_output: bool = True) -> None:
self.buffer_output = buffer_output
self.buffer: List[str] = []
self.in_section = False
self.section: List[str] = []
self.batch = False
self.verbose = False
self.use_colors = True
self.json = False
self.__level = 0
self.__is_color_supported = ('colorama' in sys.modules) or (os.name == 'posix')
self.line_ended = True
def _print(self, level: str, s: str = '', line_ended: bool = True) -> None:
'''Saves output to buffer (if in buffered mode), or immediately prints to stdout otherwise.'''
# If we're logging only 'warn' or above, and this is an 'info', ignore message.
if self.get_level(level) < self.__level:
return
if self.use_colors and self.colors_supported and len(s) > 0 and level != 'info':
s = "\033[0;%dm%s\033[0m" % (self.COLORS[level], s)
if self.buffer_output:
# Select which list to add to. If we are in a 'with' statement, then this goes in the section buffer, otherwise the general buffer.
buf = self.section if self.in_section else self.buffer
# Determine if a new line should be added, or if the last line should be appended.
if not self.line_ended:
last_entry = -1 if len(buf) > 0 else 0
buf[last_entry] = buf[last_entry] + s
else:
buf.append(s)
# When False, this tells the next call to append to the last line we just added.
self.line_ended = line_ended
else:
print(s)
def get_buffer(self) -> str:
'''Returns all buffered output, then clears the buffer.'''
self.flush_section()
buffer_str = "\n".join(self.buffer)
self.buffer = []
return buffer_str
def write(self) -> None:
'''Writes the output to stdout.'''
self.flush_section()
print(self.get_buffer(), flush=True)
def reset(self) -> None:
self.flush_section()
self.get_buffer()
@property
def level(self) -> str:
'''Returns the minimum level for output.'''
if self.__level < len(self.LEVELS):
return self.LEVELS[self.__level]
return 'unknown'
@level.setter
def level(self, name: str) -> None:
'''Sets the minimum level for output (one of: 'info', 'warn', 'fail').'''
self.__level = self.get_level(name)
def get_level(self, name: str) -> int:
cname = 'info' if name == 'good' else name
if cname not in self.LEVELS:
return sys.maxsize
return self.LEVELS.index(cname)
@property
def colors_supported(self) -> bool:
'''Returns True if the system supports color output.'''
return self.__is_color_supported
# When used in a 'with' block, the output to goes into a section; this can be sorted separately when add_section_to_buffer() is later called.
def __enter__(self) -> 'OutputBuffer':
self.in_section = True
return self
def __exit__(self, *args: Any) -> None:
self.in_section = False
def flush_section(self, sort_section: bool = False) -> None:
'''Appends section output (optionally sorting it first) to the end of the buffer, then clears the section output.'''
if sort_section:
self.section.sort()
self.buffer.extend(self.section)
self.section = []
def is_section_empty(self) -> bool:
'''Returns True if the section buffer is empty, otherwise False.'''
return len(self.section) == 0
def head(self, s: str, line_ended: bool = True) -> 'OutputBuffer':
if not self.batch:
self._print('head', s, line_ended)
return self
def fail(self, s: str, line_ended: bool = True) -> 'OutputBuffer':
self._print('fail', s, line_ended)
return self
def warn(self, s: str, line_ended: bool = True) -> 'OutputBuffer':
self._print('warn', s, line_ended)
return self
def info(self, s: str, line_ended: bool = True) -> 'OutputBuffer':
self._print('info', s, line_ended)
return self
def good(self, s: str, line_ended: bool = True) -> 'OutputBuffer':
self._print('good', s, line_ended)
return self
def sep(self) -> 'OutputBuffer':
if not self.batch:
self._print('info')
return self
def v(self, s: str, write_now: bool = False) -> 'OutputBuffer':
'''Prints a message if verbose output is enabled.'''
if self.verbose:
self.info(s)
if write_now:
self.write()
return self

517
src/ssh_audit/policy.py Normal file
View File

@ -0,0 +1,517 @@
"""
The MIT License (MIT)
Copyright (C) 2020 Joe Testa (jtesta@positronsecurity.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import sys
from typing import Dict, List, Tuple
from typing import Optional, Any, Union, cast
from datetime import date
from ssh_audit import exitcodes
from ssh_audit.ssh2_kex import SSH2_Kex # pylint: disable=unused-import
from ssh_audit.banner import Banner # pylint: disable=unused-import
# Validates policy files and performs policy testing
class Policy:
# Each field maps directly to a private member variable of the Policy class.
BUILTIN_POLICIES: Dict[str, Dict[str, Union[Optional[str], Optional[List[str]], bool, Dict[str, int]]]] = {
# Ubuntu Server policies
'Hardened Ubuntu Server 16.04 LTS (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256@libssh.org', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True},
'Hardened Ubuntu Server 18.04 LTS (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True},
'Hardened Ubuntu Server 20.04 LTS (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {'rsa-sha2-256': 4096, 'rsa-sha2-512': 4096}, 'cakey_sizes': {'rsa-sha2-256-cert-v01@openssh.com': 4096, 'rsa-sha2-512-cert-v01@openssh.com': 4096}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True},
# Generic OpenSSH Server policies
'Hardened OpenSSH Server v7.7 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v7.8 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v7.9 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v8.0 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v8.1 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v8.2 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {'rsa-sha2-256': 4096, 'rsa-sha2-512': 4096}, 'cakey_sizes': {'rsa-sha2-256-cert-v01@openssh.com': 4096, 'rsa-sha2-512-cert-v01@openssh.com': 4096}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v8.3 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {'rsa-sha2-256': 4096, 'rsa-sha2-512': 4096}, 'cakey_sizes': {'rsa-sha2-256-cert-v01@openssh.com': 4096, 'rsa-sha2-512-cert-v01@openssh.com': 4096}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v8.4 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {'rsa-sha2-256': 4096, 'rsa-sha2-512': 4096}, 'cakey_sizes': {'rsa-sha2-256-cert-v01@openssh.com': 4096, 'rsa-sha2-512-cert-v01@openssh.com': 4096}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v8.5 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {'rsa-sha2-256': 4096, 'rsa-sha2-512': 4096}, 'cakey_sizes': {'rsa-sha2-256-cert-v01@openssh.com': 4096, 'rsa-sha2-512-cert-v01@openssh.com': 4096}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True},
# Ubuntu Client policies
'Hardened Ubuntu Client 16.04 LTS (version 2)': {'version': '2', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256', 'rsa-sha2-512'], 'optional_host_keys': None, 'kex': ['curve25519-sha256@libssh.org', 'diffie-hellman-group-exchange-sha256', 'ext-info-c'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False},
'Hardened Ubuntu Client 18.04 LTS (version 2)': {'version': '2', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256', 'rsa-sha2-512'], 'optional_host_keys': None, 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'ext-info-c'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False},
'Hardened Ubuntu Client 20.04 LTS (version 2)': {'version': '2', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512', 'rsa-sha2-512-cert-v01@openssh.com'], 'optional_host_keys': None, 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'ext-info-c'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False},
}
def __init__(self, policy_file: Optional[str] = None, policy_data: Optional[str] = None, manual_load: bool = False) -> None:
self._name: Optional[str] = None
self._version: Optional[str] = None
self._banner: Optional[str] = None
self._compressions: Optional[List[str]] = None
self._host_keys: Optional[List[str]] = None
self._optional_host_keys: Optional[List[str]] = None
self._kex: Optional[List[str]] = None
self._ciphers: Optional[List[str]] = None
self._macs: Optional[List[str]] = None
self._hostkey_sizes: Optional[Dict[str, int]] = None
self._cakey_sizes: Optional[Dict[str, int]] = None
self._dh_modulus_sizes: Optional[Dict[str, int]] = None
self._server_policy = True
self._name_and_version: str = ''
# Ensure that only one mode was specified.
num_modes = 0
if policy_file is not None:
num_modes += 1
if policy_data is not None:
num_modes += 1
if manual_load is True:
num_modes += 1
if num_modes != 1:
raise RuntimeError('Exactly one of the following can be specified only: policy_file, policy_data, or manual_load')
if manual_load:
return
if policy_file is not None:
try:
with open(policy_file, "r") as f:
policy_data = f.read()
except FileNotFoundError:
print("Error: policy file not found: %s" % policy_file)
sys.exit(exitcodes.UNKNOWN_ERROR)
lines = []
if policy_data is not None:
lines = policy_data.split("\n")
for line in lines:
line = line.strip()
if (len(line) == 0) or line.startswith('#'):
continue
key = None
val = None
try:
key, val = line.split('=')
except ValueError as ve:
raise ValueError("could not parse line: %s" % line) from ve
key = key.strip()
val = val.strip()
if key not in ['name', 'version', 'banner', 'compressions', 'host keys', 'optional host keys', 'key exchanges', 'ciphers', 'macs', 'client policy'] and not key.startswith('hostkey_size_') and not key.startswith('cakey_size_') and not key.startswith('dh_modulus_size_'):
raise ValueError("invalid field found in policy: %s" % line)
if key in ['name', 'banner']:
# If the banner value is blank, set it to "" so that the code below handles it.
if len(val) < 2:
val = "\"\""
if (val[0] != '"') or (val[-1] != '"'):
raise ValueError('the value for the %s field must be enclosed in quotes: %s' % (key, val))
# Remove the surrounding quotes, and unescape quotes & newlines.
val = val[1:-1]. replace("\\\"", "\"").replace("\\n", "\n")
if key == 'name':
self._name = val
elif key == 'banner':
self._banner = val
elif key == 'version':
self._version = val
elif key in ['compressions', 'host keys', 'optional host keys', 'key exchanges', 'ciphers', 'macs']:
try:
algs = val.split(',')
except ValueError:
# If the value has no commas, then set the algorithm list to just the value.
algs = [val]
# Strip whitespace in each algorithm name.
algs = [alg.strip() for alg in algs]
if key == 'compressions':
self._compressions = algs
elif key == 'host keys':
self._host_keys = algs
elif key == 'optional host keys':
self._optional_host_keys = algs
elif key == 'key exchanges':
self._kex = algs
elif key == 'ciphers':
self._ciphers = algs
elif key == 'macs':
self._macs = algs
elif key.startswith('hostkey_size_'):
hostkey_type = key[13:]
if self._hostkey_sizes is None:
self._hostkey_sizes = {}
self._hostkey_sizes[hostkey_type] = int(val)
elif key.startswith('cakey_size_'):
cakey_type = key[11:]
if self._cakey_sizes is None:
self._cakey_sizes = {}
self._cakey_sizes[cakey_type] = int(val)
elif key.startswith('dh_modulus_size_'):
dh_modulus_type = key[16:]
if self._dh_modulus_sizes is None:
self._dh_modulus_sizes = {}
self._dh_modulus_sizes[dh_modulus_type] = int(val)
elif key.startswith('client policy') and val.lower() == 'true':
self._server_policy = False
if self._name is None:
raise ValueError('The policy does not have a name field.')
if self._version is None:
raise ValueError('The policy does not have a version field.')
self._name_and_version = "%s (version %s)" % (self._name, self._version)
@staticmethod
def _append_error(errors: List[Any], mismatched_field: str, expected_required: Optional[List[str]], expected_optional: Optional[List[str]], actual: List[str]) -> None:
if expected_required is None:
expected_required = ['']
if expected_optional is None:
expected_optional = ['']
errors.append({'mismatched_field': mismatched_field, 'expected_required': expected_required, 'expected_optional': expected_optional, 'actual': actual})
@staticmethod
def create(source: Optional[str], banner: Optional['Banner'], kex: Optional['SSH2_Kex'], client_audit: bool) -> str:
'''Creates a policy based on a server configuration. Returns a string.'''
today = date.today().strftime('%Y/%m/%d')
compressions = None
host_keys = None
kex_algs = None
ciphers = None
macs = None
rsa_hostkey_sizes_str = ''
rsa_cakey_sizes_str = ''
dh_modulus_sizes_str = ''
client_policy_str = ''
if client_audit:
client_policy_str = "\n# Set to true to signify this is a policy for clients, not servers.\nclient policy = true\n"
if kex is not None:
if kex.server.compression is not None:
compressions = ', '.join(kex.server.compression)
if kex.key_algorithms is not None:
host_keys = ', '.join(kex.key_algorithms)
if kex.kex_algorithms is not None:
kex_algs = ', '.join(kex.kex_algorithms)
if kex.server.encryption is not None:
ciphers = ', '.join(kex.server.encryption)
if kex.server.mac is not None:
macs = ', '.join(kex.server.mac)
if kex.rsa_key_sizes():
rsa_key_sizes_dict = kex.rsa_key_sizes()
for host_key_type in sorted(rsa_key_sizes_dict):
hostkey_size, cakey_size = rsa_key_sizes_dict[host_key_type]
rsa_hostkey_sizes_str = "%shostkey_size_%s = %d\n" % (rsa_hostkey_sizes_str, host_key_type, hostkey_size)
if cakey_size != -1:
rsa_cakey_sizes_str = "%scakey_size_%s = %d\n" % (rsa_cakey_sizes_str, host_key_type, cakey_size)
if len(rsa_hostkey_sizes_str) > 0:
rsa_hostkey_sizes_str = "\n# RSA host key sizes.\n%s" % rsa_hostkey_sizes_str
if len(rsa_cakey_sizes_str) > 0:
rsa_cakey_sizes_str = "\n# RSA CA key sizes.\n%s" % rsa_cakey_sizes_str
if kex.dh_modulus_sizes():
dh_modulus_sizes_dict = kex.dh_modulus_sizes()
for gex_type in sorted(dh_modulus_sizes_dict):
modulus_size, _ = dh_modulus_sizes_dict[gex_type]
dh_modulus_sizes_str = "%sdh_modulus_size_%s = %d\n" % (dh_modulus_sizes_str, gex_type, modulus_size)
if len(dh_modulus_sizes_str) > 0:
dh_modulus_sizes_str = "\n# Group exchange DH modulus sizes.\n%s" % dh_modulus_sizes_str
policy_data = '''#
# Custom policy based on %s (created on %s)
#
%s
# The name of this policy (displayed in the output during scans). Must be in quotes.
name = "Custom Policy (based on %s on %s)"
# The version of this policy (displayed in the output during scans). Not parsed, and may be any value, including strings.
version = 1
# The banner that must match exactly. Commented out to ignore banners, since minor variability in the banner is sometimes normal.
# banner = "%s"
# The compression options that must match exactly (order matters). Commented out to ignore by default.
# compressions = %s
%s%s%s
# The host key types that must match exactly (order matters).
host keys = %s
# Host key types that may optionally appear.
#optional host keys = ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com
# The key exchange algorithms that must match exactly (order matters).
key exchanges = %s
# The ciphers that must match exactly (order matters).
ciphers = %s
# The MACs that must match exactly (order matters).
macs = %s
''' % (source, today, client_policy_str, source, today, banner, compressions, rsa_hostkey_sizes_str, rsa_cakey_sizes_str, dh_modulus_sizes_str, host_keys, kex_algs, ciphers, macs)
return policy_data
def evaluate(self, banner: Optional['Banner'], kex: Optional['SSH2_Kex']) -> Tuple[bool, List[Dict[str, str]], str]:
'''Evaluates a server configuration against this policy. Returns a tuple of a boolean (True if server adheres to policy) and an array of strings that holds error messages.'''
ret = True
errors: List[Any] = []
banner_str = str(banner)
if (self._banner is not None) and (banner_str != self._banner):
ret = False
self._append_error(errors, 'Banner', [self._banner], None, [banner_str])
# All subsequent tests require a valid kex, so end here if we don't have one.
if kex is None:
return ret, errors, self._get_error_str(errors)
if (self._compressions is not None) and (kex.server.compression != self._compressions):
ret = False
self._append_error(errors, 'Compression', self._compressions, None, kex.server.compression)
# If a list of optional host keys was given in the policy, remove any of its entries from the list retrieved from the server. This allows us to do an exact comparison with the expected list below.
pruned_host_keys = kex.key_algorithms
if self._optional_host_keys is not None:
pruned_host_keys = [x for x in kex.key_algorithms if x not in self._optional_host_keys]
if (self._host_keys is not None) and (pruned_host_keys != self._host_keys):
ret = False
self._append_error(errors, 'Host keys', self._host_keys, self._optional_host_keys, kex.key_algorithms)
if self._hostkey_sizes is not None:
hostkey_types = list(self._hostkey_sizes.keys())
hostkey_types.sort() # Sorted to make testing output repeatable.
for hostkey_type in hostkey_types:
expected_hostkey_size = self._hostkey_sizes[hostkey_type]
if hostkey_type in kex.rsa_key_sizes():
actual_hostkey_size, actual_cakey_size = kex.rsa_key_sizes()[hostkey_type]
if actual_hostkey_size != expected_hostkey_size:
ret = False
self._append_error(errors, 'RSA host key (%s) sizes' % hostkey_type, [str(expected_hostkey_size)], None, [str(actual_hostkey_size)])
if self._cakey_sizes is not None:
hostkey_types = list(self._cakey_sizes.keys())
hostkey_types.sort() # Sorted to make testing output repeatable.
for hostkey_type in hostkey_types:
expected_cakey_size = self._cakey_sizes[hostkey_type]
if hostkey_type in kex.rsa_key_sizes():
actual_hostkey_size, actual_cakey_size = kex.rsa_key_sizes()[hostkey_type]
if actual_cakey_size != expected_cakey_size:
ret = False
self._append_error(errors, 'RSA CA key (%s) sizes' % hostkey_type, [str(expected_cakey_size)], None, [str(actual_cakey_size)])
if kex.kex_algorithms != self._kex:
ret = False
self._append_error(errors, 'Key exchanges', self._kex, None, kex.kex_algorithms)
if (self._ciphers is not None) and (kex.server.encryption != self._ciphers):
ret = False
self._append_error(errors, 'Ciphers', self._ciphers, None, kex.server.encryption)
if (self._macs is not None) and (kex.server.mac != self._macs):
ret = False
self._append_error(errors, 'MACs', self._macs, None, kex.server.mac)
if self._dh_modulus_sizes is not None:
dh_modulus_types = list(self._dh_modulus_sizes.keys())
dh_modulus_types.sort() # Sorted to make testing output repeatable.
for dh_modulus_type in dh_modulus_types:
expected_dh_modulus_size = self._dh_modulus_sizes[dh_modulus_type]
if dh_modulus_type in kex.dh_modulus_sizes():
actual_dh_modulus_size, _ = kex.dh_modulus_sizes()[dh_modulus_type]
if expected_dh_modulus_size != actual_dh_modulus_size:
ret = False
self._append_error(errors, 'Group exchange (%s) modulus sizes' % dh_modulus_type, [str(expected_dh_modulus_size)], None, [str(actual_dh_modulus_size)])
return ret, errors, self._get_error_str(errors)
@staticmethod
def _get_error_str(errors: List[Any]) -> str:
'''Transforms an error struct to a flat string of error messages.'''
error_list = []
spacer = ''
for e in errors:
e_str = " * %s did not match.\n" % e['mismatched_field']
if ('expected_optional' in e) and (e['expected_optional'] != ['']):
e_str += " - Expected (required): %s\n - Expected (optional): %s\n" % (Policy._normalize_error_field(e['expected_required']), Policy._normalize_error_field(e['expected_optional']))
spacer = ' '
else:
e_str += " - Expected: %s\n" % Policy._normalize_error_field(e['expected_required'])
spacer = ' '
e_str += " - Actual:%s%s\n" % (spacer, Policy._normalize_error_field(e['actual']))
error_list.append(e_str)
error_list.sort() # To ensure repeatable results for testing.
error_str = ''
if len(error_list) > 0:
error_str = "\n".join(error_list)
return error_str
def get_name_and_version(self) -> str:
'''Returns a string of this Policy's name and version.'''
return self._name_and_version
def is_server_policy(self) -> bool:
'''Returns True if this is a server policy, or False if this is a client policy.'''
return self._server_policy
@staticmethod
def list_builtin_policies() -> Tuple[List[str], List[str]]:
'''Returns two lists: a list of names of built-in server policies, and a list of names of built-in client policies, respectively.'''
server_policy_names = []
client_policy_names = []
for policy_name in Policy.BUILTIN_POLICIES:
if Policy.BUILTIN_POLICIES[policy_name]['server_policy']:
server_policy_names.append(policy_name)
else:
client_policy_names.append(policy_name)
server_policy_names.sort()
client_policy_names.sort()
return server_policy_names, client_policy_names
@staticmethod
def load_builtin_policy(policy_name: str) -> Optional['Policy']:
'''Returns a Policy with the specified built-in policy name loaded, or None if no policy of that name exists.'''
p = None
if policy_name in Policy.BUILTIN_POLICIES:
policy_struct = Policy.BUILTIN_POLICIES[policy_name]
p = Policy(manual_load=True)
policy_name_without_version = policy_name[0:policy_name.rfind(' (')]
p._name = policy_name_without_version # pylint: disable=protected-access
p._version = cast(str, policy_struct['version']) # pylint: disable=protected-access
p._banner = cast(Optional[str], policy_struct['banner']) # pylint: disable=protected-access
p._compressions = cast(Optional[List[str]], policy_struct['compressions']) # pylint: disable=protected-access
p._host_keys = cast(Optional[List[str]], policy_struct['host_keys']) # pylint: disable=protected-access
p._optional_host_keys = cast(Optional[List[str]], policy_struct['optional_host_keys']) # pylint: disable=protected-access
p._kex = cast(Optional[List[str]], policy_struct['kex']) # pylint: disable=protected-access
p._ciphers = cast(Optional[List[str]], policy_struct['ciphers']) # pylint: disable=protected-access
p._macs = cast(Optional[List[str]], policy_struct['macs']) # pylint: disable=protected-access
p._hostkey_sizes = cast(Optional[Dict[str, int]], policy_struct['hostkey_sizes']) # pylint: disable=protected-access
p._cakey_sizes = cast(Optional[Dict[str, int]], policy_struct['cakey_sizes']) # pylint: disable=protected-access
p._dh_modulus_sizes = cast(Optional[Dict[str, int]], policy_struct['dh_modulus_sizes']) # pylint: disable=protected-access
p._server_policy = cast(bool, policy_struct['server_policy']) # pylint: disable=protected-access
p._name_and_version = "%s (version %s)" % (p._name, p._version) # pylint: disable=protected-access
return p
@staticmethod
def _normalize_error_field(field: List[str]) -> Any:
'''If field is an array with a string parsable as an integer, return that integer. Otherwise, return the field joined with commas.'''
if len(field) == 1:
try:
return int(field[0])
except ValueError:
return field[0]
else:
return ', '.join(field)
def __str__(self) -> str:
undefined = '{undefined}'
name = undefined
version = undefined
banner = undefined
compressions_str = undefined
host_keys_str = undefined
optional_host_keys_str = undefined
kex_str = undefined
ciphers_str = undefined
macs_str = undefined
hostkey_sizes_str = undefined
cakey_sizes_str = undefined
dh_modulus_sizes_str = undefined
if self._name is not None:
name = '[%s]' % self._name
if self._version is not None:
version = '[%s]' % self._version
if self._banner is not None:
banner = '[%s]' % self._banner
if self._compressions is not None:
compressions_str = ', '.join(self._compressions)
if self._host_keys is not None:
host_keys_str = ', '.join(self._host_keys)
if self._optional_host_keys is not None:
optional_host_keys_str = ', '.join(self._optional_host_keys)
if self._kex is not None:
kex_str = ', '.join(self._kex)
if self._ciphers is not None:
ciphers_str = ', '.join(self._ciphers)
if self._macs is not None:
macs_str = ', '.join(self._macs)
if self._hostkey_sizes is not None:
hostkey_sizes_str = str(self._hostkey_sizes)
if self._cakey_sizes is not None:
cakey_sizes_str = str(self._cakey_sizes)
if self._dh_modulus_sizes is not None:
dh_modulus_sizes_str = str(self._dh_modulus_sizes)
return "Name: %s\nVersion: %s\nBanner: %s\nCompressions: %s\nHost Keys: %s\nOptional Host Keys: %s\nKey Exchanges: %s\nCiphers: %s\nMACs: %s\nHost Key Sizes: %s\nCA Key Sizes: %s\nDH Modulus Sizes: %s\nServer Policy: %r" % (name, version, banner, compressions_str, host_keys_str, optional_host_keys_str, kex_str, ciphers_str, macs_str, hostkey_sizes_str, cakey_sizes_str, dh_modulus_sizes_str, self._server_policy)

31
src/ssh_audit/product.py Normal file
View File

@ -0,0 +1,31 @@
"""
The MIT License (MIT)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
class Product: # pylint: disable=too-few-public-methods
OpenSSH = 'OpenSSH'
DropbearSSH = 'Dropbear SSH'
LibSSH = 'libssh'
TinySSH = 'TinySSH'
PuTTY = 'PuTTY'

36
src/ssh_audit/protocol.py Normal file
View File

@ -0,0 +1,36 @@
"""
The MIT License (MIT)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
class Protocol: # pylint: disable=too-few-public-methods
SMSG_PUBLIC_KEY = 2
MSG_DEBUG = 4
MSG_KEXINIT = 20
MSG_NEWKEYS = 21
MSG_KEXDH_INIT = 30
MSG_KEXDH_REPLY = 31
MSG_KEXDH_GEX_REQUEST = 34
MSG_KEXDH_GEX_GROUP = 31
MSG_KEXDH_GEX_INIT = 32
MSG_KEXDH_GEX_REPLY = 33

92
src/ssh_audit/readbuf.py Normal file
View File

@ -0,0 +1,92 @@
"""
The MIT License (MIT)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import io
import struct
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
class ReadBuf:
def __init__(self, data: Optional[bytes] = None) -> None:
super(ReadBuf, self).__init__()
self._buf = io.BytesIO(data) if data is not None else io.BytesIO()
self._len = len(data) if data is not None else 0
@property
def unread_len(self) -> int:
return self._len - self._buf.tell()
def read(self, size: int) -> bytes:
return self._buf.read(size)
def read_byte(self) -> int:
v: int = struct.unpack('B', self.read(1))[0]
return v
def read_bool(self) -> bool:
return self.read_byte() != 0
def read_int(self) -> int:
v: int = struct.unpack('>I', self.read(4))[0]
return v
def read_list(self) -> List[str]:
list_size = self.read_int()
return self.read(list_size).decode('utf-8', 'replace').split(',')
def read_string(self) -> bytes:
n = self.read_int()
return self.read(n)
@classmethod
def _parse_mpint(cls, v: bytes, pad: bytes, f: str) -> int:
r = 0
if len(v) % 4 != 0:
v = pad * (4 - (len(v) % 4)) + v
for i in range(0, len(v), 4):
r = (r << 32) | struct.unpack(f, v[i:i + 4])[0]
return r
def read_mpint1(self) -> int:
# NOTE: Data Type Enc @ http://www.snailbook.com/docs/protocol-1.5.txt
bits = struct.unpack('>H', self.read(2))[0]
n = (bits + 7) // 8
return self._parse_mpint(self.read(n), b'\x00', '>I')
def read_mpint2(self) -> int:
# NOTE: Section 5 @ https://www.ietf.org/rfc/rfc4251.txt
v = self.read_string()
if len(v) == 0:
return 0
pad, f = (b'\xff', '>i') if ord(v[0:1]) & 0x80 != 0 else (b'\x00', '>I')
return self._parse_mpint(v, pad, f)
def read_line(self) -> str:
return self._buf.readline().rstrip().decode('utf-8', 'replace')
def reset(self) -> None:
self._buf = io.BytesIO()
self._len = 0

227
src/ssh_audit/software.py Normal file
View File

@ -0,0 +1,227 @@
"""
The MIT License (MIT)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import re
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
from ssh_audit.banner import Banner
from ssh_audit.product import Product
class Software:
def __init__(self, vendor: Optional[str], product: str, version: str, patch: Optional[str], os_version: Optional[str]) -> None:
self.__vendor = vendor
self.__product = product
self.__version = version
self.__patch = patch
self.__os = os_version
@property
def vendor(self) -> Optional[str]:
return self.__vendor
@property
def product(self) -> str:
return self.__product
@property
def version(self) -> str:
return self.__version
@property
def patch(self) -> Optional[str]:
return self.__patch
@property
def os(self) -> Optional[str]:
return self.__os
def compare_version(self, other: Union[None, 'Software', str]) -> int:
# pylint: disable=too-many-branches,too-many-return-statements
if other is None:
return 1
if isinstance(other, Software):
other = '{}{}'.format(other.version, other.patch or '')
else:
other = str(other)
mx = re.match(r'^([\d\.]+\d+)(.*)$', other)
if mx is not None:
oversion, opatch = mx.group(1), mx.group(2).strip()
else:
oversion, opatch = other, ''
if self.version < oversion:
return -1
elif self.version > oversion:
return 1
spatch = self.patch or ''
if self.product == Product.DropbearSSH:
if not re.match(r'^test\d.*$', opatch):
opatch = 'z{}'.format(opatch)
if not re.match(r'^test\d.*$', spatch):
spatch = 'z{}'.format(spatch)
elif self.product == Product.OpenSSH:
mx1 = re.match(r'^p(\d).*', opatch)
mx2 = re.match(r'^p(\d).*', spatch)
if not (bool(mx1) and bool(mx2)):
if mx1 is not None:
opatch = mx1.group(1)
if mx2 is not None:
spatch = mx2.group(1)
# OpenBSD version and p1 versions are considered the same.
if ((spatch == '') and (opatch == '1')) or ((spatch == '1') and (opatch == '')):
return 0
if spatch < opatch:
return -1
elif spatch > opatch:
return 1
return 0
def between_versions(self, vfrom: str, vtill: str) -> bool:
if bool(vfrom) and self.compare_version(vfrom) < 0:
return False
if bool(vtill) and self.compare_version(vtill) > 0:
return False
return True
def display(self, full: bool = True) -> str:
r = '{} '.format(self.vendor) if bool(self.vendor) else ''
r += self.product
if bool(self.version):
r += ' {}'.format(self.version)
if full:
patch = self.patch or ''
if self.product == Product.OpenSSH:
mx = re.match(r'^(p\d)(.*)$', patch)
if mx is not None:
r += mx.group(1)
patch = mx.group(2).strip()
if bool(patch):
r += ' ({})'.format(patch)
if bool(self.os):
r += ' running on {}'.format(self.os)
return r
def __str__(self) -> str:
return self.display()
def __repr__(self) -> str:
r = 'vendor={}, '.format(self.vendor) if bool(self.vendor) else ''
r += 'product={}'.format(self.product)
if bool(self.version):
r += ', version={}'.format(self.version)
if bool(self.patch):
r += ', patch={}'.format(self.patch)
if bool(self.os):
r += ', os={}'.format(self.os)
return '<{}({})>'.format(self.__class__.__name__, r)
@staticmethod
def _fix_patch(patch: str) -> Optional[str]:
return re.sub(r'^[-_\.]+', '', patch) or None
@staticmethod
def _fix_date(d: Optional[str]) -> Optional[str]:
if d is not None and len(d) == 8:
return '{}-{}-{}'.format(d[:4], d[4:6], d[6:8])
else:
return None
@classmethod
def _extract_os_version(cls, c: Optional[str]) -> Optional[str]:
if c is None:
return None
mx = re.match(r'^NetBSD(?:_Secure_Shell)?(?:[\s-]+(\d{8})(.*))?$', c)
if mx is not None:
d = cls._fix_date(mx.group(1))
return 'NetBSD' if d is None else 'NetBSD ({})'.format(d)
mx = re.match(r'^FreeBSD(?:\slocalisations)?[\s-]+(\d{8})(.*)$', c)
if not bool(mx):
mx = re.match(r'^[^@]+@FreeBSD\.org[\s-]+(\d{8})(.*)$', c)
if mx is not None:
d = cls._fix_date(mx.group(1))
return 'FreeBSD' if d is None else 'FreeBSD ({})'.format(d)
w = ['RemotelyAnywhere', 'DesktopAuthority', 'RemoteSupportManager']
for win_soft in w:
mx = re.match(r'^in ' + win_soft + r' ([\d\.]+\d)$', c)
if mx is not None:
ver = mx.group(1)
return 'Microsoft Windows ({} {})'.format(win_soft, ver)
generic = ['NetBSD', 'FreeBSD']
for g in generic:
if c.startswith(g) or c.endswith(g):
return g
return None
@classmethod
def parse(cls, banner: 'Banner') -> Optional['Software']:
# pylint: disable=too-many-return-statements
software = str(banner.software)
mx = re.match(r'^dropbear_([\d\.]+\d+)(.*)', software)
v: Optional[str] = None
if mx is not None:
patch = cls._fix_patch(mx.group(2))
v, p = 'Matt Johnston', Product.DropbearSSH
v = None
return cls(v, p, mx.group(1), patch, None)
mx = re.match(r'^OpenSSH[_\.-]+([\d\.]+\d+)(.*)', software)
if mx is not None:
patch = cls._fix_patch(mx.group(2))
v, p = 'OpenBSD', Product.OpenSSH
v = None
os_version = cls._extract_os_version(banner.comments)
return cls(v, p, mx.group(1), patch, os_version)
mx = re.match(r'^libssh-([\d\.]+\d+)(.*)', software)
if mx is not None:
patch = cls._fix_patch(mx.group(2))
v, p = None, Product.LibSSH
os_version = cls._extract_os_version(banner.comments)
return cls(v, p, mx.group(1), patch, os_version)
mx = re.match(r'^libssh_([\d\.]+\d+)(.*)', software)
if mx is not None:
patch = cls._fix_patch(mx.group(2))
v, p = None, Product.LibSSH
os_version = cls._extract_os_version(banner.comments)
return cls(v, p, mx.group(1), patch, os_version)
mx = re.match(r'^RomSShell_([\d\.]+\d+)(.*)', software)
if mx is not None:
patch = cls._fix_patch(mx.group(2))
v, p = 'Allegro Software', 'RomSShell'
return cls(v, p, mx.group(1), patch, None)
mx = re.match(r'^mpSSH_([\d\.]+\d+)', software)
if mx is not None:
v, p = 'HP', 'iLO (Integrated Lights-Out) sshd'
return cls(v, p, mx.group(1), None, None)
mx = re.match(r'^Cisco-([\d\.]+\d+)', software)
if mx is not None:
v, p = 'Cisco', 'IOS/PIX sshd'
return cls(v, p, mx.group(1), None, None)
mx = re.match(r'^tinyssh_(.*)', software)
if mx is not None:
return cls(None, Product.TinySSH, mx.group(1), None, None)
mx = re.match(r'^PuTTY_Release_(.*)', software)
if mx:
return cls(None, Product.PuTTY, mx.group(1), None, None)
return None

40
src/ssh_audit/ssh1.py Normal file
View File

@ -0,0 +1,40 @@
"""
The MIT License (MIT)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
from ssh_audit.ssh1_crc32 import SSH1_CRC32
class SSH1:
_crc32: Optional[SSH1_CRC32] = None
CIPHERS = ['none', 'idea', 'des', '3des', 'tss', 'rc4', 'blowfish']
AUTHS = ['none', 'rhosts', 'rsa', 'password', 'rhosts_rsa', 'tis', 'kerberos']
@classmethod
def crc32(cls, v: bytes) -> int:
if cls._crc32 is None:
cls._crc32 = SSH1_CRC32()
return cls._crc32.calc(v)

View File

@ -0,0 +1,47 @@
"""
The MIT License (MIT)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
class SSH1_CRC32:
def __init__(self) -> None:
self._table = [0] * 256
for i in range(256):
crc = 0
n = i
for _ in range(8):
x = (crc ^ n) & 1
crc = (crc >> 1) ^ (x * 0xedb88320)
n = n >> 1
self._table[i] = crc
def calc(self, v: bytes) -> int:
crc, length = 0, len(v)
for i in range(length):
n = ord(v[i:i + 1])
n = n ^ (crc & 0xff)
crc = (crc >> 8) ^ self._table[n]
return crc

View File

@ -0,0 +1,58 @@
"""
The MIT License (MIT)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
class SSH1_KexDB: # pylint: disable=too-few-public-methods
FAIL_PLAINTEXT = 'no encryption/integrity'
FAIL_OPENSSH37_REMOVE = 'removed since OpenSSH 3.7'
FAIL_NA_BROKEN = 'not implemented in OpenSSH, broken algorithm'
FAIL_NA_UNSAFE = 'not implemented in OpenSSH (server), unsafe algorithm'
TEXT_CIPHER_IDEA = 'cipher used by commercial SSH'
ALGORITHMS: Dict[str, Dict[str, List[List[Optional[str]]]]] = {
'key': {
'ssh-rsa1': [['1.2.2']],
},
'enc': {
'none': [['1.2.2'], [FAIL_PLAINTEXT]],
'idea': [[None], [], [], [TEXT_CIPHER_IDEA]],
'des': [['2.3.0C'], [FAIL_NA_UNSAFE]],
'3des': [['1.2.2']],
'tss': [[''], [FAIL_NA_BROKEN]],
'rc4': [[], [FAIL_NA_BROKEN]],
'blowfish': [['1.2.2']],
},
'aut': {
'rhosts': [['1.2.2', '3.6'], [FAIL_OPENSSH37_REMOVE]],
'rsa': [['1.2.2']],
'password': [['1.2.2']],
'rhosts_rsa': [['1.2.2']],
'tis': [['1.2.2']],
'kerberos': [['1.2.2', '3.6'], [FAIL_OPENSSH37_REMOVE]],
}
}

View File

@ -0,0 +1,144 @@
"""
The MIT License (MIT)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
from ssh_audit.ssh1 import SSH1
from ssh_audit.readbuf import ReadBuf
from ssh_audit.utils import Utils
from ssh_audit.writebuf import WriteBuf
class SSH1_PublicKeyMessage:
def __init__(self, cookie: bytes, skey: Tuple[int, int, int], hkey: Tuple[int, int, int], pflags: int, cmask: int, amask: int) -> None:
if len(skey) != 3:
raise ValueError('invalid server key pair: {}'.format(skey))
if len(hkey) != 3:
raise ValueError('invalid host key pair: {}'.format(hkey))
self.__cookie = cookie
self.__server_key = skey
self.__host_key = hkey
self.__protocol_flags = pflags
self.__supported_ciphers_mask = cmask
self.__supported_authentications_mask = amask
@property
def cookie(self) -> bytes:
return self.__cookie
@property
def server_key_bits(self) -> int:
return self.__server_key[0]
@property
def server_key_public_exponent(self) -> int:
return self.__server_key[1]
@property
def server_key_public_modulus(self) -> int:
return self.__server_key[2]
@property
def host_key_bits(self) -> int:
return self.__host_key[0]
@property
def host_key_public_exponent(self) -> int:
return self.__host_key[1]
@property
def host_key_public_modulus(self) -> int:
return self.__host_key[2]
@property
def host_key_fingerprint_data(self) -> bytes:
# pylint: disable=protected-access
mod = WriteBuf._create_mpint(self.host_key_public_modulus, False)
e = WriteBuf._create_mpint(self.host_key_public_exponent, False)
return mod + e
@property
def protocol_flags(self) -> int:
return self.__protocol_flags
@property
def supported_ciphers_mask(self) -> int:
return self.__supported_ciphers_mask
@property
def supported_ciphers(self) -> List[str]:
ciphers = []
for i in range(len(SSH1.CIPHERS)):
if self.__supported_ciphers_mask & (1 << i) != 0:
ciphers.append(Utils.to_text(SSH1.CIPHERS[i]))
return ciphers
@property
def supported_authentications_mask(self) -> int:
return self.__supported_authentications_mask
@property
def supported_authentications(self) -> List[str]:
auths = []
for i in range(1, len(SSH1.AUTHS)):
if self.__supported_authentications_mask & (1 << i) != 0:
auths.append(Utils.to_text(SSH1.AUTHS[i]))
return auths
def write(self, wbuf: 'WriteBuf') -> None:
wbuf.write(self.cookie)
wbuf.write_int(self.server_key_bits)
wbuf.write_mpint1(self.server_key_public_exponent)
wbuf.write_mpint1(self.server_key_public_modulus)
wbuf.write_int(self.host_key_bits)
wbuf.write_mpint1(self.host_key_public_exponent)
wbuf.write_mpint1(self.host_key_public_modulus)
wbuf.write_int(self.protocol_flags)
wbuf.write_int(self.supported_ciphers_mask)
wbuf.write_int(self.supported_authentications_mask)
@property
def payload(self) -> bytes:
wbuf = WriteBuf()
self.write(wbuf)
return wbuf.write_flush()
@classmethod
def parse(cls, payload: bytes) -> 'SSH1_PublicKeyMessage':
buf = ReadBuf(payload)
cookie = buf.read(8)
server_key_bits = buf.read_int()
server_key_exponent = buf.read_mpint1()
server_key_modulus = buf.read_mpint1()
skey = (server_key_bits, server_key_exponent, server_key_modulus)
host_key_bits = buf.read_int()
host_key_exponent = buf.read_mpint1()
host_key_modulus = buf.read_mpint1()
hkey = (host_key_bits, host_key_exponent, host_key_modulus)
pflags = buf.read_int()
cmask = buf.read_int()
amask = buf.read_int()
pkm = cls(cookie, skey, hkey, pflags, cmask, amask)
return pkm

136
src/ssh_audit/ssh2_kex.py Normal file
View File

@ -0,0 +1,136 @@
"""
The MIT License (MIT)
Copyright (C) 2017-2020 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
from ssh_audit.ssh2_kexparty import SSH2_KexParty
from ssh_audit.readbuf import ReadBuf
from ssh_audit.writebuf import WriteBuf
class SSH2_Kex:
def __init__(self, cookie: bytes, kex_algs: List[str], key_algs: List[str], cli: 'SSH2_KexParty', srv: 'SSH2_KexParty', follows: bool, unused: int = 0) -> None:
self.__cookie = cookie
self.__kex_algs = kex_algs
self.__key_algs = key_algs
self.__client = cli
self.__server = srv
self.__follows = follows
self.__unused = unused
self.__rsa_key_sizes: Dict[str, Tuple[int, int]] = {}
self.__dh_modulus_sizes: Dict[str, Tuple[int, int]] = {}
self.__host_keys: Dict[str, bytes] = {}
@property
def cookie(self) -> bytes:
return self.__cookie
@property
def kex_algorithms(self) -> List[str]:
return self.__kex_algs
@property
def key_algorithms(self) -> List[str]:
return self.__key_algs
# client_to_server
@property
def client(self) -> 'SSH2_KexParty':
return self.__client
# server_to_client
@property
def server(self) -> 'SSH2_KexParty':
return self.__server
@property
def follows(self) -> bool:
return self.__follows
@property
def unused(self) -> int:
return self.__unused
def set_rsa_key_size(self, rsa_type: str, hostkey_size: int, ca_size: int = -1) -> None:
self.__rsa_key_sizes[rsa_type] = (hostkey_size, ca_size)
def rsa_key_sizes(self) -> Dict[str, Tuple[int, int]]:
return self.__rsa_key_sizes
def set_dh_modulus_size(self, gex_alg: str, modulus_size: int) -> None:
self.__dh_modulus_sizes[gex_alg] = (modulus_size, -1)
def dh_modulus_sizes(self) -> Dict[str, Tuple[int, int]]:
return self.__dh_modulus_sizes
def set_host_key(self, key_type: str, hostkey: bytes) -> None:
self.__host_keys[key_type] = hostkey
def host_keys(self) -> Dict[str, bytes]:
return self.__host_keys
def write(self, wbuf: 'WriteBuf') -> None:
wbuf.write(self.cookie)
wbuf.write_list(self.kex_algorithms)
wbuf.write_list(self.key_algorithms)
wbuf.write_list(self.client.encryption)
wbuf.write_list(self.server.encryption)
wbuf.write_list(self.client.mac)
wbuf.write_list(self.server.mac)
wbuf.write_list(self.client.compression)
wbuf.write_list(self.server.compression)
wbuf.write_list(self.client.languages)
wbuf.write_list(self.server.languages)
wbuf.write_bool(self.follows)
wbuf.write_int(self.__unused)
@property
def payload(self) -> bytes:
wbuf = WriteBuf()
self.write(wbuf)
return wbuf.write_flush()
@classmethod
def parse(cls, payload: bytes) -> 'SSH2_Kex':
buf = ReadBuf(payload)
cookie = buf.read(16)
kex_algs = buf.read_list()
key_algs = buf.read_list()
cli_enc = buf.read_list()
srv_enc = buf.read_list()
cli_mac = buf.read_list()
srv_mac = buf.read_list()
cli_compression = buf.read_list()
srv_compression = buf.read_list()
cli_languages = buf.read_list()
srv_languages = buf.read_list()
follows = buf.read_bool()
unused = buf.read_int()
cli = SSH2_KexParty(cli_enc, cli_mac, cli_compression, cli_languages)
srv = SSH2_KexParty(srv_enc, srv_mac, srv_compression, srv_languages)
kex = cls(cookie, kex_algs, key_algs, cli, srv, follows, unused)
return kex

274
src/ssh_audit/ssh2_kexdb.py Normal file
View File

@ -0,0 +1,274 @@
"""
The MIT License (MIT)
Copyright (C) 2017-2020 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
class SSH2_KexDB: # pylint: disable=too-few-public-methods
WARN_OPENSSH74_UNSAFE = 'disabled (in client) since OpenSSH 7.4, unsafe algorithm'
WARN_OPENSSH72_LEGACY = 'disabled (in client) since OpenSSH 7.2, legacy algorithm'
FAIL_OPENSSH70_LEGACY = 'removed since OpenSSH 7.0, legacy algorithm'
FAIL_OPENSSH70_WEAK = 'removed (in server) and disabled (in client) since OpenSSH 7.0, weak algorithm'
FAIL_OPENSSH70_LOGJAM = 'disabled (in client) since OpenSSH 7.0, logjam attack'
INFO_OPENSSH69_CHACHA = 'default cipher since OpenSSH 6.9.'
FAIL_OPENSSH67_UNSAFE = 'removed (in server) since OpenSSH 6.7, unsafe algorithm'
FAIL_OPENSSH61_REMOVE = 'removed since OpenSSH 6.1, removed from specification'
FAIL_OPENSSH31_REMOVE = 'removed since OpenSSH 3.1'
INFO_OPENSSH82_FUTURE_DEPRECATION = 'a future deprecation notice has been issued in OpenSSH 8.2: https://www.openssh.com/txt/release-8.2'
FAIL_DBEAR67_DISABLED = 'disabled since Dropbear SSH 2015.67'
FAIL_DBEAR53_DISABLED = 'disabled since Dropbear SSH 0.53'
FAIL_DEPRECATED_CIPHER = 'deprecated cipher'
FAIL_WEAK_CIPHER = 'using weak cipher'
FAIL_WEAK_ALGORITHM = 'using weak/obsolete algorithm'
FAIL_PLAINTEXT = 'no encryption/integrity'
FAIL_DEPRECATED_MAC = 'deprecated MAC'
FAIL_1024BIT_MODULUS = 'using small 1024-bit modulus'
FAIL_UNPROVEN = 'using unproven algorithm'
FAIL_HASH_WEAK = 'using weak hashing algorithm'
WARN_CURVES_WEAK = 'using weak elliptic curves'
WARN_RNDSIG_KEY = 'using weak random number generator could reveal the key'
WARN_HASH_WEAK = 'using weak hashing algorithm'
WARN_CIPHER_MODE = 'using weak cipher mode'
WARN_BLOCK_SIZE = 'using small 64-bit block size'
WARN_CIPHER_WEAK = 'using weak cipher'
WARN_ENCRYPT_AND_MAC = 'using encrypt-and-MAC mode'
WARN_TAG_SIZE = 'using small 64-bit tag size'
WARN_TAG_SIZE_96 = 'using small 96-bit tag size'
WARN_EXPERIMENTAL = 'using experimental algorithm'
WARN_OBSOLETE = 'using obsolete algorithm'
WARN_UNTRUSTED = 'using untrusted algorithm'
ALGORITHMS: Dict[str, Dict[str, List[List[Optional[str]]]]] = {
# Format: 'algorithm_name': [['version_first_appeared_in'], [reason_for_failure1, reason_for_failure2, ...], [warning1, warning2, ...]]
'kex': {
'diffie-hellman-group1-sha1': [['2.3.0,d0.28,l10.2', '6.6', '6.9'], [FAIL_1024BIT_MODULUS, FAIL_OPENSSH67_UNSAFE, FAIL_OPENSSH70_LOGJAM], [WARN_HASH_WEAK]],
'gss-group1-sha1-toWM5Slw5Ew8Mqkay+al2g==': [[], [FAIL_1024BIT_MODULUS, FAIL_OPENSSH67_UNSAFE, FAIL_OPENSSH70_LOGJAM], [WARN_HASH_WEAK]],
'gss-gex-sha1-toWM5Slw5Ew8Mqkay+al2g==': [[], [], [WARN_HASH_WEAK]],
'gss-gex-sha1-': [[], [], [WARN_HASH_WEAK]],
'gss-group1-sha1-': [[], [FAIL_1024BIT_MODULUS], [WARN_HASH_WEAK]],
'gss-group14-sha1-': [[], [], [WARN_HASH_WEAK]],
'gss-group14-sha1-toWM5Slw5Ew8Mqkay+al2g==': [[], [], [WARN_HASH_WEAK]],
'gss-group14-sha256-': [[]],
'gss-group14-sha256-toWM5Slw5Ew8Mqkay+al2g==': [[]],
'gss-group15-sha512-': [[]],
'gss-group15-sha512-toWM5Slw5Ew8Mqkay+al2g==': [[]],
'gss-group16-sha512-': [[]],
'gss-nistp256-sha256-': [[], [WARN_CURVES_WEAK]],
'gss-curve25519-sha256-': [[]],
'diffie-hellman-group1-sha256': [[], [FAIL_1024BIT_MODULUS]],
'diffie-hellman-group14-sha1': [['3.9,d0.53,l10.6.0'], [], [WARN_HASH_WEAK]],
'diffie-hellman-group14-sha256': [['7.3,d2016.73']],
'diffie-hellman-group14-sha256@ssh.com': [[]],
'diffie-hellman-group15-sha256': [[]],
'diffie-hellman-group15-sha256@ssh.com': [[]],
'diffie-hellman-group15-sha384@ssh.com': [[]],
'diffie-hellman-group15-sha512': [[]],
'diffie-hellman-group16-sha256': [[]],
'diffie-hellman-group16-sha384@ssh.com': [[]],
'diffie-hellman-group16-sha512': [['7.3,d2016.73']],
'diffie-hellman-group16-sha512@ssh.com': [[]],
'diffie-hellman-group17-sha512': [[]],
'diffie-hellman-group18-sha512': [['7.3']],
'diffie-hellman-group18-sha512@ssh.com': [[]],
'diffie-hellman-group-exchange-sha1': [['2.3.0', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_HASH_WEAK]],
'diffie-hellman-group-exchange-sha256': [['4.4']],
'diffie-hellman-group-exchange-sha256@ssh.com': [[]],
'diffie-hellman-group-exchange-sha512@ssh.com': [[]],
'ecdh-sha2-curve25519': [[], []],
'ecdh-sha2-nistb233': [[], [WARN_CURVES_WEAK]],
'ecdh-sha2-nistb409': [[], [WARN_CURVES_WEAK]],
'ecdh-sha2-nistk163': [[], [WARN_CURVES_WEAK]],
'ecdh-sha2-nistk233': [[], [WARN_CURVES_WEAK]],
'ecdh-sha2-nistk283': [[], [WARN_CURVES_WEAK]],
'ecdh-sha2-nistk409': [[], [WARN_CURVES_WEAK]],
'ecdh-sha2-nistp192': [[], [WARN_CURVES_WEAK]],
'ecdh-sha2-nistp224': [[], [WARN_CURVES_WEAK]],
'ecdh-sha2-nistp256': [['5.7,d2013.62,l10.6.0'], [WARN_CURVES_WEAK]],
'ecdh-sha2-nistp384': [['5.7,d2013.62'], [WARN_CURVES_WEAK]],
'ecdh-sha2-nistp521': [['5.7,d2013.62'], [WARN_CURVES_WEAK]],
'ecdh-sha2-nistt571': [[], [WARN_CURVES_WEAK]],
'ecdh-sha2-1.3.132.0.10': [[]], # ECDH over secp256k1 (i.e.: the Bitcoin curve)
'curve25519-sha256@libssh.org': [['6.5,d2013.62,l10.6.0']],
'curve25519-sha256': [['7.4,d2018.76']],
'curve448-sha512': [[]],
'kexguess2@matt.ucc.asn.au': [['d2013.57']],
'rsa1024-sha1': [[], [FAIL_1024BIT_MODULUS], [WARN_HASH_WEAK]],
'rsa2048-sha256': [[]],
'sntrup4591761x25519-sha512@tinyssh.org': [['8.0', '8.4'], [], [WARN_EXPERIMENTAL]],
'sntrup761x25519-sha512@openssh.com': [['8.5'], [], [WARN_EXPERIMENTAL]],
'kexAlgoCurve25519SHA256': [[]],
'Curve25519SHA256': [[]],
'ext-info-c': [[]], # Extension negotiation (RFC 8308)
'ext-info-s': [[]], # Extension negotiation (RFC 8308)
},
'key': {
'ssh-rsa1': [[], [FAIL_WEAK_ALGORITHM]],
'rsa-sha2-256': [['7.2']],
'rsa-sha2-512': [['7.2']],
'ssh-ed25519': [['6.5,l10.7.0']],
'ssh-ed25519-cert-v01@openssh.com': [['6.5']],
'ssh-rsa': [['2.5.0,d0.28,l10.2'], [FAIL_HASH_WEAK], [], [INFO_OPENSSH82_FUTURE_DEPRECATION]],
'ssh-dss': [['2.1.0,d0.28,l10.2', '6.9'], [FAIL_1024BIT_MODULUS, FAIL_OPENSSH70_WEAK], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-nistp256': [['5.7,d2013.62,l10.6.4'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-nistp384': [['5.7,d2013.62,l10.6.4'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-nistp521': [['5.7,d2013.62,l10.6.4'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-1.3.132.0.10': [[], [], [WARN_RNDSIG_KEY]], # ECDSA over secp256k1 (i.e.: the Bitcoin curve)
'x509v3-sign-dss': [[], [FAIL_1024BIT_MODULUS, FAIL_OPENSSH70_WEAK], [WARN_RNDSIG_KEY]],
'x509v3-sign-rsa': [[], [FAIL_HASH_WEAK], [], [INFO_OPENSSH82_FUTURE_DEPRECATION]],
'x509v3-sign-rsa-sha256@ssh.com': [[]],
'x509v3-ssh-dss': [[], [FAIL_1024BIT_MODULUS, FAIL_OPENSSH70_WEAK], [WARN_RNDSIG_KEY]],
'x509v3-ssh-rsa': [[], [FAIL_HASH_WEAK], [], [INFO_OPENSSH82_FUTURE_DEPRECATION]],
'ssh-rsa-cert-v00@openssh.com': [['5.4', '6.9'], [FAIL_OPENSSH70_LEGACY, FAIL_HASH_WEAK], [], [INFO_OPENSSH82_FUTURE_DEPRECATION]],
'ssh-dss-cert-v00@openssh.com': [['5.4', '6.9'], [FAIL_1024BIT_MODULUS, FAIL_OPENSSH70_LEGACY], [WARN_RNDSIG_KEY]],
'ssh-rsa-cert-v01@openssh.com': [['5.6'], [FAIL_HASH_WEAK], [], [INFO_OPENSSH82_FUTURE_DEPRECATION]],
'ssh-dss-cert-v01@openssh.com': [['5.6', '6.9'], [FAIL_1024BIT_MODULUS, FAIL_OPENSSH70_WEAK], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-nistp256-cert-v01@openssh.com': [['5.7'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-nistp384-cert-v01@openssh.com': [['5.7'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-nistp521-cert-v01@openssh.com': [['5.7'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
'rsa-sha2-256-cert-v01@openssh.com': [['7.8']],
'rsa-sha2-512-cert-v01@openssh.com': [['7.8']],
'ssh-rsa-sha256@ssh.com': [[]],
'ssh-dss-sha256@ssh.com': [[], [FAIL_1024BIT_MODULUS]],
'sk-ecdsa-sha2-nistp256-cert-v01@openssh.com': [['8.2'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
'sk-ecdsa-sha2-nistp256@openssh.com': [['8.2'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
'sk-ssh-ed25519-cert-v01@openssh.com': [['8.2']],
'sk-ssh-ed25519@openssh.com': [['8.2']],
'ssh-gost2001': [[], [], [WARN_UNTRUSTED]],
'ssh-gost2012-256': [[], [], [WARN_UNTRUSTED]],
'ssh-gost2012-512': [[], [], [WARN_UNTRUSTED]],
'spi-sign-rsa': [[]],
'ssh-ed448': [[]],
'x509v3-ecdsa-sha2-nistp256': [[], [WARN_CURVES_WEAK]],
'x509v3-ecdsa-sha2-nistp384': [[], [WARN_CURVES_WEAK]],
'x509v3-ecdsa-sha2-nistp521': [[], [WARN_CURVES_WEAK]],
'x509v3-rsa2048-sha256': [[]],
},
'enc': {
'none': [['1.2.2,d2013.56,l10.2'], [FAIL_PLAINTEXT]],
'des': [[], [FAIL_WEAK_CIPHER], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
'des-cbc': [[], [FAIL_WEAK_CIPHER], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
'des-cbc@ssh.com': [[], [FAIL_WEAK_CIPHER], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
'des-cbc-ssh1': [[], [FAIL_WEAK_CIPHER], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
'3des': [[], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH74_UNSAFE, WARN_CIPHER_WEAK, WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
'3des-cbc': [['1.2.2,d0.28,l10.2', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH74_UNSAFE, WARN_CIPHER_WEAK, WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
'3des-ctr': [['d0.52'], [FAIL_WEAK_CIPHER]],
'blowfish': [[], [FAIL_WEAK_ALGORITHM], [WARN_BLOCK_SIZE]],
'blowfish-cbc': [['1.2.2,d0.28,l10.2', '6.6,d0.52', '7.1,d0.52'], [FAIL_OPENSSH67_UNSAFE, FAIL_DBEAR53_DISABLED], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
'blowfish-ctr': [[], [FAIL_OPENSSH67_UNSAFE, FAIL_DBEAR53_DISABLED], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
'twofish-cbc': [['d0.28', 'd2014.66'], [FAIL_DBEAR67_DISABLED], [WARN_CIPHER_MODE]],
'twofish128-cbc': [['d0.47', 'd2014.66'], [FAIL_DBEAR67_DISABLED], [WARN_CIPHER_MODE]],
'twofish192-cbc': [[], [], [WARN_CIPHER_MODE]],
'twofish256-cbc': [['d0.47', 'd2014.66'], [FAIL_DBEAR67_DISABLED], [WARN_CIPHER_MODE]],
'twofish-ctr': [[]],
'twofish128-ctr': [['d2015.68']],
'twofish192-ctr': [[]],
'twofish256-ctr': [['d2015.68']],
'serpent128-cbc': [[], [FAIL_DEPRECATED_CIPHER], [WARN_CIPHER_MODE]],
'serpent192-cbc': [[], [FAIL_DEPRECATED_CIPHER], [WARN_CIPHER_MODE]],
'serpent256-cbc': [[], [FAIL_DEPRECATED_CIPHER], [WARN_CIPHER_MODE]],
'serpent128-ctr': [[], [FAIL_DEPRECATED_CIPHER]],
'serpent192-ctr': [[], [FAIL_DEPRECATED_CIPHER]],
'serpent256-ctr': [[], [FAIL_DEPRECATED_CIPHER]],
'idea-cbc': [[], [FAIL_DEPRECATED_CIPHER], [WARN_CIPHER_MODE]],
'idea-ctr': [[], [FAIL_DEPRECATED_CIPHER]],
'cast128-ctr': [[], [FAIL_DEPRECATED_CIPHER]],
'cast128-cbc': [['2.1.0', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
'arcfour': [['2.1.0', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_WEAK]],
'arcfour128': [['4.2', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_WEAK]],
'arcfour256': [['4.2', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_WEAK]],
'aes128-cbc': [['2.3.0,d0.28,l10.2', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_CIPHER_MODE]],
'aes192-cbc': [['2.3.0,l10.2', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_CIPHER_MODE]],
'aes256-cbc': [['2.3.0,d0.47,l10.2', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_CIPHER_MODE]],
'rijndael128-cbc': [['2.3.0', '3.0.2'], [FAIL_OPENSSH31_REMOVE], [WARN_CIPHER_MODE]],
'rijndael192-cbc': [['2.3.0', '3.0.2'], [FAIL_OPENSSH31_REMOVE], [WARN_CIPHER_MODE]],
'rijndael256-cbc': [['2.3.0', '3.0.2'], [FAIL_OPENSSH31_REMOVE], [WARN_CIPHER_MODE]],
'rijndael-cbc@lysator.liu.se': [['2.3.0', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_MODE]],
'aes128-ctr': [['3.7,d0.52,l10.4.1']],
'aes192-ctr': [['3.7,l10.4.1']],
'aes256-ctr': [['3.7,d0.52,l10.4.1']],
'aes128-gcm': [[]],
'aes256-gcm': [[]],
'AEAD_AES_128_GCM': [[]],
'AEAD_AES_256_GCM': [[]],
'aes128-gcm@openssh.com': [['6.2']],
'aes256-gcm@openssh.com': [['6.2']],
'chacha20-poly1305': [[], [], [], [INFO_OPENSSH69_CHACHA]],
'chacha20-poly1305@openssh.com': [['6.5'], [], [], [INFO_OPENSSH69_CHACHA]],
'camellia128-cbc': [[], [], [WARN_CIPHER_MODE]],
'camellia128-ctr': [[]],
'camellia192-cbc': [[], [], [WARN_CIPHER_MODE]],
'camellia192-ctr': [[]],
'camellia256-cbc': [[], [], [WARN_CIPHER_MODE]],
'camellia256-ctr': [[]],
'crypticore128@ssh.com': [[], [FAIL_UNPROVEN]],
'seed-cbc@ssh.com': [[], [], [WARN_OBSOLETE, WARN_CIPHER_MODE]],
},
'mac': {
'none': [['d2013.56'], [FAIL_PLAINTEXT]],
'hmac-sha1': [['2.1.0,d0.28,l10.2'], [], [WARN_ENCRYPT_AND_MAC, WARN_HASH_WEAK]],
'hmac-sha1-96': [['2.5.0,d0.47', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_ENCRYPT_AND_MAC, WARN_HASH_WEAK]],
'hmac-sha2-56': [[], [], [WARN_TAG_SIZE, WARN_ENCRYPT_AND_MAC]],
'hmac-sha2-224': [[], [], [WARN_TAG_SIZE, WARN_ENCRYPT_AND_MAC]],
'hmac-sha2-256': [['5.9,d2013.56,l10.7.0'], [], [WARN_ENCRYPT_AND_MAC]],
'hmac-sha2-256-96': [['5.9', '6.0'], [FAIL_OPENSSH61_REMOVE], [WARN_ENCRYPT_AND_MAC]],
'hmac-sha2-384': [[], [], [WARN_ENCRYPT_AND_MAC]],
'hmac-sha2-512': [['5.9,d2013.56,l10.7.0'], [], [WARN_ENCRYPT_AND_MAC]],
'hmac-sha2-512-96': [['5.9', '6.0'], [FAIL_OPENSSH61_REMOVE], [WARN_ENCRYPT_AND_MAC]],
'hmac-sha3-224': [[], [], [WARN_ENCRYPT_AND_MAC]],
'hmac-sha3-256': [[], [], [WARN_ENCRYPT_AND_MAC]],
'hmac-sha3-384': [[], [], [WARN_ENCRYPT_AND_MAC]],
'hmac-sha3-512': [[], [], [WARN_ENCRYPT_AND_MAC]],
'hmac-sha256': [[], [], [WARN_ENCRYPT_AND_MAC]],
'hmac-sha256-96@ssh.com': [[], [], [WARN_ENCRYPT_AND_MAC, WARN_TAG_SIZE]],
'hmac-sha256@ssh.com': [[], [], [WARN_ENCRYPT_AND_MAC]],
'hmac-sha512': [[], [], [WARN_ENCRYPT_AND_MAC]],
'hmac-sha512@ssh.com': [[], [], [WARN_ENCRYPT_AND_MAC]],
'hmac-md5': [['2.1.0,d0.28', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_ENCRYPT_AND_MAC, WARN_HASH_WEAK]],
'hmac-md5-96': [['2.5.0', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_ENCRYPT_AND_MAC, WARN_HASH_WEAK]],
'hmac-ripemd': [[], [FAIL_DEPRECATED_MAC], [WARN_OPENSSH72_LEGACY, WARN_ENCRYPT_AND_MAC]],
'hmac-ripemd160': [['2.5.0', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_ENCRYPT_AND_MAC]],
'hmac-ripemd160@openssh.com': [['2.1.0', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_ENCRYPT_AND_MAC]],
'umac-64@openssh.com': [['4.7'], [], [WARN_ENCRYPT_AND_MAC, WARN_TAG_SIZE]],
'umac-128@openssh.com': [['6.2'], [], [WARN_ENCRYPT_AND_MAC]],
'hmac-sha1-etm@openssh.com': [['6.2'], [], [WARN_HASH_WEAK]],
'hmac-sha1-96-etm@openssh.com': [['6.2', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_HASH_WEAK]],
'hmac-sha2-256-96-etm@openssh.com': [[], [], [WARN_TAG_SIZE_96]], # Despite the @openssh.com tag, it doesn't appear that this was ever shipped with OpenSSH; it is only implemented in AsyncSSH (?).
'hmac-sha2-512-96-etm@openssh.com': [[], [], [WARN_TAG_SIZE_96]], # Despite the @openssh.com tag, it doesn't appear that this was ever shipped with OpenSSH; it is only implemented in AsyncSSH (?).
'hmac-sha2-256-etm@openssh.com': [['6.2']],
'hmac-sha2-512-etm@openssh.com': [['6.2']],
'hmac-md5-etm@openssh.com': [['6.2', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_HASH_WEAK]],
'hmac-md5-96-etm@openssh.com': [['6.2', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_HASH_WEAK]],
'hmac-ripemd160-etm@openssh.com': [['6.2', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY]],
'umac-32@openssh.com': [[], [], [WARN_ENCRYPT_AND_MAC, WARN_TAG_SIZE]], # Despite having the @openssh.com suffix, this may never have shipped with OpenSSH (!).
'umac-64-etm@openssh.com': [['6.2'], [], [WARN_TAG_SIZE]],
'umac-96@openssh.com': [[], [], [WARN_ENCRYPT_AND_MAC]], # Despite having the @openssh.com suffix, this may never have shipped with OpenSSH (!).
'umac-128-etm@openssh.com': [['6.2']],
'aes128-gcm': [[]],
'aes256-gcm': [[]],
'chacha20-poly1305@openssh.com': [[]], # Despite the @openssh.com tag, this was never shipped as a MAC in OpenSSH (only as a cipher); it is only implemented as a MAC in Syncplify.
'crypticore-mac@ssh.com': [[], [FAIL_UNPROVEN]],
}
}

View File

@ -0,0 +1,50 @@
"""
The MIT License (MIT)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
class SSH2_KexParty:
def __init__(self, enc: List[str], mac: List[str], compression: List[str], languages: List[str]) -> None:
self.__enc = enc
self.__mac = mac
self.__compression = compression
self.__languages = languages
@property
def encryption(self) -> List[str]:
return self.__enc
@property
def mac(self) -> List[str]:
return self.__mac
@property
def compression(self) -> List[str]:
return self.__compression
@property
def languages(self) -> List[str]:
return self.__languages

1108
src/ssh_audit/ssh_audit.py Executable file

File diff suppressed because it is too large Load Diff

337
src/ssh_audit/ssh_socket.py Normal file
View File

@ -0,0 +1,337 @@
"""
The MIT License (MIT)
Copyright (C) 2017-2021 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import errno
import os
import select
import socket
import struct
import sys
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
from ssh_audit import exitcodes
from ssh_audit.banner import Banner
from ssh_audit.globals import SSH_HEADER
from ssh_audit.outputbuffer import OutputBuffer
from ssh_audit.protocol import Protocol
from ssh_audit.readbuf import ReadBuf
from ssh_audit.ssh1 import SSH1
from ssh_audit.ssh2_kex import SSH2_Kex
from ssh_audit.ssh2_kexparty import SSH2_KexParty
from ssh_audit.utils import Utils
from ssh_audit.writebuf import WriteBuf
class SSH_Socket(ReadBuf, WriteBuf):
class InsufficientReadException(Exception):
pass
SM_BANNER_SENT = 1
def __init__(self, host: Optional[str], port: int, ip_version_preference: List[int] = [], timeout: Union[int, float] = 5, timeout_set: bool = False) -> None: # pylint: disable=dangerous-default-value
super(SSH_Socket, self).__init__()
self.__sock: Optional[socket.socket] = None
self.__sock_map: Dict[int, socket.socket] = {}
self.__block_size = 8
self.__state = 0
self.__header: List[str] = []
self.__banner: Optional[Banner] = None
if host is None:
raise ValueError('undefined host')
nport = Utils.parse_int(port)
if nport < 1 or nport > 65535:
raise ValueError('invalid port: {}'.format(port))
self.__host = host
self.__port = nport
self.__ip_version_preference = ip_version_preference # Holds only 5 possible values: [] (no preference), [4] (use IPv4 only), [6] (use IPv6 only), [46] (use both IPv4 and IPv6, but prioritize v4), and [64] (use both IPv4 and IPv6, but prioritize v6).
self.__timeout = timeout
self.__timeout_set = timeout_set
self.client_host: Optional[str] = None
self.client_port = None
def _resolve(self) -> Iterable[Tuple[int, Tuple[Any, ...]]]:
# If __ip_version_preference has only one entry, then it means that ONLY that IP version should be used.
if len(self.__ip_version_preference) == 1:
family = socket.AF_INET if self.__ip_version_preference[0] == 4 else socket.AF_INET6
else:
family = socket.AF_UNSPEC
try:
stype = socket.SOCK_STREAM
r = socket.getaddrinfo(self.__host, self.__port, family, stype)
# If the user has a preference for using IPv4 over IPv6 (or vice-versa), then sort the list returned by getaddrinfo() so that the preferred address type comes first.
if len(self.__ip_version_preference) == 2:
r = sorted(r, key=lambda x: x[0], reverse=(self.__ip_version_preference[0] == 6))
for af, socktype, _proto, _canonname, addr in r:
if socktype == socket.SOCK_STREAM:
yield af, addr
except socket.error as e:
OutputBuffer().fail('[exception] {}'.format(e)).write()
sys.exit(exitcodes.CONNECTION_ERROR)
# Listens on a server socket and accepts one connection (used for
# auditing client connections).
def listen_and_accept(self) -> None:
try:
# Socket to listen on all IPv4 addresses.
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('0.0.0.0', self.__port))
s.listen()
self.__sock_map[s.fileno()] = s
except Exception:
print("Warning: failed to listen on any IPv4 interfaces.")
try:
# Socket to listen on all IPv6 addresses.
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
s.bind(('::', self.__port))
s.listen()
self.__sock_map[s.fileno()] = s
except Exception:
print("Warning: failed to listen on any IPv6 interfaces.")
# If we failed to listen on any interfaces, terminate.
if len(self.__sock_map.keys()) == 0:
print("Error: failed to listen on any IPv4 and IPv6 interfaces!")
sys.exit(exitcodes.CONNECTION_ERROR)
# Wait for an incoming connection. If a timeout was explicitly
# set by the user, terminate when it elapses.
fds = None
time_elapsed = 0.0
interval = 1.0
while True:
# Wait for a connection on either socket.
fds = select.select(self.__sock_map.keys(), [], [], interval)
time_elapsed += interval
# We have incoming data on at least one of the sockets.
if len(fds[0]) > 0:
break
if self.__timeout_set and time_elapsed >= self.__timeout:
print("Timeout elapsed. Terminating...")
sys.exit(exitcodes.CONNECTION_ERROR)
# Accept the connection.
c, addr = self.__sock_map[fds[0][0]].accept()
self.client_host = addr[0]
self.client_port = addr[1]
c.settimeout(self.__timeout)
self.__sock = c
def connect(self) -> Optional[str]:
'''Returns None on success, or an error string.'''
err = None
for af, addr in self._resolve():
s = None
try:
s = socket.socket(af, socket.SOCK_STREAM)
s.settimeout(self.__timeout)
s.connect(addr)
self.__sock = s
return None
except socket.error as e:
err = e
self._close_socket(s)
if err is None:
errm = 'host {} has no DNS records'.format(self.__host)
else:
errt = (self.__host, self.__port, err)
errm = 'cannot connect to {} port {}: {}'.format(*errt)
return '[exception] {}'.format(errm)
def get_banner(self, sshv: int = 2) -> Tuple[Optional['Banner'], List[str], Optional[str]]:
if self.__sock is None:
return self.__banner, self.__header, 'not connected'
if self.__banner is not None:
return self.__banner, self.__header, None
banner = SSH_HEADER.format('1.5' if sshv == 1 else '2.0')
if self.__state < self.SM_BANNER_SENT:
self.send_banner(banner)
s = 0
e = None
while s >= 0:
s, e = self.recv()
if s < 0:
continue
while self.unread_len > 0:
line = self.read_line()
if len(line.strip()) == 0:
continue
self.__banner = Banner.parse(line)
if self.__banner is not None:
return self.__banner, self.__header, None
self.__header.append(line)
return self.__banner, self.__header, e
def recv(self, size: int = 2048) -> Tuple[int, Optional[str]]:
if self.__sock is None:
return -1, 'not connected'
try:
data = self.__sock.recv(size)
except socket.timeout:
return -1, 'timed out'
except socket.error as e:
if e.args[0] in (errno.EAGAIN, errno.EWOULDBLOCK):
return 0, 'retry'
return -1, str(e.args[-1])
if len(data) == 0:
return -1, None
pos = self._buf.tell()
self._buf.seek(0, 2)
self._buf.write(data)
self._len += len(data)
self._buf.seek(pos, 0)
return len(data), None
def send(self, data: bytes) -> Tuple[int, Optional[str]]:
if self.__sock is None:
return -1, 'not connected'
try:
self.__sock.send(data)
return 0, None
except socket.error as e:
return -1, str(e.args[-1])
# Send a KEXINIT with the lists of key exchanges, hostkeys, ciphers, MACs, compressions, and languages that we "support".
def send_kexinit(self, key_exchanges: List[str] = ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'ecdh-sha2-nistp256', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp521', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group14-sha256'], hostkeys: List[str] = ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ssh-ed25519'], ciphers: List[str] = ['chacha20-poly1305@openssh.com', 'aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-gcm@openssh.com', 'aes256-gcm@openssh.com'], macs: List[str] = ['umac-64-etm@openssh.com', 'umac-128-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'hmac-sha1-etm@openssh.com', 'umac-64@openssh.com', 'umac-128@openssh.com', 'hmac-sha2-256', 'hmac-sha2-512', 'hmac-sha1'], compressions: List[str] = ['none', 'zlib@openssh.com'], languages: List[str] = ['']) -> None: # pylint: disable=dangerous-default-value
'''Sends the list of supported host keys, key exchanges, ciphers, and MACs. Emulates OpenSSH v8.2.'''
kexparty = SSH2_KexParty(ciphers, macs, compressions, languages)
kex = SSH2_Kex(os.urandom(16), key_exchanges, hostkeys, kexparty, kexparty, False, 0)
self.write_byte(Protocol.MSG_KEXINIT)
kex.write(self)
self.send_packet()
def send_banner(self, banner: str) -> None:
self.send(banner.encode() + b'\r\n')
if self.__state < self.SM_BANNER_SENT:
self.__state = self.SM_BANNER_SENT
def ensure_read(self, size: int) -> None:
while self.unread_len < size:
s, e = self.recv()
if s < 0:
raise SSH_Socket.InsufficientReadException(e)
def read_packet(self, sshv: int = 2) -> Tuple[int, bytes]:
try:
header = WriteBuf()
self.ensure_read(4)
packet_length = self.read_int()
header.write_int(packet_length)
# XXX: validate length
if sshv == 1:
padding_length = 8 - packet_length % 8
self.ensure_read(padding_length)
padding = self.read(padding_length)
header.write(padding)
payload_length = packet_length
check_size = padding_length + payload_length
else:
self.ensure_read(1)
padding_length = self.read_byte()
header.write_byte(padding_length)
payload_length = packet_length - padding_length - 1
check_size = 4 + 1 + payload_length + padding_length
if check_size % self.__block_size != 0:
OutputBuffer().fail('[exception] invalid ssh packet (block size)').write()
sys.exit(exitcodes.CONNECTION_ERROR)
self.ensure_read(payload_length)
if sshv == 1:
payload = self.read(payload_length - 4)
header.write(payload)
crc = self.read_int()
header.write_int(crc)
else:
payload = self.read(payload_length)
header.write(payload)
packet_type = ord(payload[0:1])
if sshv == 1:
rcrc = SSH1.crc32(padding + payload)
if crc != rcrc:
OutputBuffer().fail('[exception] packet checksum CRC32 mismatch.').write()
sys.exit(exitcodes.CONNECTION_ERROR)
else:
self.ensure_read(padding_length)
padding = self.read(padding_length)
payload = payload[1:]
return packet_type, payload
except SSH_Socket.InsufficientReadException as ex:
if ex.args[0] is None:
header.write(self.read(self.unread_len))
e = header.write_flush().strip()
else:
e = ex.args[0].encode('utf-8')
return -1, e
def send_packet(self) -> Tuple[int, Optional[str]]:
payload = self.write_flush()
padding = -(len(payload) + 5) % 8
if padding < 4:
padding += 8
plen = len(payload) + padding + 1
pad_bytes = b'\x00' * padding
data = struct.pack('>Ib', plen, padding) + payload + pad_bytes
return self.send(data)
def is_connected(self) -> bool:
"""Returns true if this Socket is connected, False otherwise."""
return self.__sock is not None
def close(self) -> None:
self.__cleanup()
self.reset()
self.__state = 0
self.__header = []
self.__banner = None
def _close_socket(self, s: Optional[socket.socket]) -> None: # pylint: disable=no-self-use
try:
if s is not None:
s.shutdown(socket.SHUT_RDWR)
s.close() # pragma: nocover
except Exception:
pass
def __del__(self) -> None:
self.__cleanup()
def __cleanup(self) -> None:
self._close_socket(self.__sock)
for fd in self.__sock_map:
self._close_socket(self.__sock_map[fd])
self.__sock = None

View File

@ -0,0 +1,77 @@
"""
The MIT License (MIT)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
from ssh_audit.algorithm import Algorithm
class Timeframe:
def __init__(self) -> None:
self.__storage: Dict[str, List[Optional[str]]] = {}
def __contains__(self, product: str) -> bool:
return product in self.__storage
def __getitem__(self, product: str) -> Sequence[Optional[str]]:
return tuple(self.__storage.get(product, [None] * 4))
def __str__(self) -> str:
return self.__storage.__str__()
def __repr__(self) -> str:
return self.__str__()
def get_from(self, product: str, for_server: bool = True) -> Optional[str]:
return self[product][0 if bool(for_server) else 2]
def get_till(self, product: str, for_server: bool = True) -> Optional[str]:
return self[product][1 if bool(for_server) else 3]
def _update(self, versions: Optional[str], pos: int) -> None:
ssh_versions: Dict[str, str] = {}
for_srv, for_cli = pos < 2, pos > 1
for v in (versions or '').split(','):
ssh_prod, ssh_ver, is_cli = Algorithm.get_ssh_version(v)
if not ssh_ver or (is_cli and for_srv) or (not is_cli and for_cli and ssh_prod in ssh_versions):
continue
ssh_versions[ssh_prod] = ssh_ver
for ssh_product, ssh_version in ssh_versions.items():
if ssh_product not in self.__storage:
self.__storage[ssh_product] = [None] * 4
prev = self[ssh_product][pos]
if (prev is None or (prev < ssh_version and pos % 2 == 0) or (prev > ssh_version and pos % 2 == 1)):
self.__storage[ssh_product][pos] = ssh_version
def update(self, versions: List[Optional[str]], for_server: Optional[bool] = None) -> 'Timeframe':
for_cli = for_server is None or for_server is False
for_srv = for_server is None or for_server is True
vlen = len(versions)
for i in range(min(3, vlen)):
if for_srv and i < 2:
self._update(versions[i], i)
if for_cli and (i % 2 == 0 or vlen == 2):
self._update(versions[i], 3 - 0**i)
return self

165
src/ssh_audit/utils.py Normal file
View File

@ -0,0 +1,165 @@
"""
The MIT License (MIT)
Copyright (C) 2017-2020 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import ipaddress
import re
import sys
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
class Utils:
@classmethod
def _type_err(cls, v: Any, target: str) -> TypeError:
return TypeError('cannot convert {} to {}'.format(type(v), target))
@classmethod
def to_bytes(cls, v: Union[bytes, str], enc: str = 'utf-8') -> bytes:
if isinstance(v, bytes):
return v
elif isinstance(v, str):
return v.encode(enc)
raise cls._type_err(v, 'bytes')
@classmethod
def to_text(cls, v: Union[str, bytes], enc: str = 'utf-8') -> str:
if isinstance(v, str):
return v
elif isinstance(v, bytes):
return v.decode(enc)
raise cls._type_err(v, 'unicode text')
@classmethod
def _is_ascii(cls, v: str, char_filter: Callable[[int], bool] = lambda x: x <= 127) -> bool:
r = False
if isinstance(v, str):
for c in v:
i = cls.ctoi(c)
if not char_filter(i):
return r
r = True
return r
@classmethod
def _to_ascii(cls, v: str, char_filter: Callable[[int], bool] = lambda x: x <= 127, errors: str = 'replace') -> str:
if isinstance(v, str):
r = bytearray()
for c in v:
i = cls.ctoi(c)
if char_filter(i):
r.append(i)
else:
if errors == 'ignore':
continue
r.append(63)
return cls.to_text(r.decode('ascii'))
raise cls._type_err(v, 'ascii')
@classmethod
def is_ascii(cls, v: str) -> bool:
return cls._is_ascii(v)
@classmethod
def to_ascii(cls, v: str, errors: str = 'replace') -> str:
return cls._to_ascii(v, errors=errors)
@classmethod
def is_print_ascii(cls, v: str) -> bool:
return cls._is_ascii(v, lambda x: 126 >= x >= 32)
@classmethod
def to_print_ascii(cls, v: str, errors: str = 'replace') -> str:
return cls._to_ascii(v, lambda x: 126 >= x >= 32, errors)
@classmethod
def unique_seq(cls, seq: Sequence[Any]) -> Sequence[Any]:
seen: Set[Any] = set()
def _seen_add(x: Any) -> bool:
seen.add(x)
return False
if isinstance(seq, tuple):
return tuple(x for x in seq if x not in seen and not _seen_add(x))
else:
return [x for x in seq if x not in seen and not _seen_add(x)]
@classmethod
def ctoi(cls, c: Union[str, int]) -> int:
if isinstance(c, str):
return ord(c[0])
else:
return c
@staticmethod
def parse_int(v: Any) -> int:
try:
return int(v)
except ValueError:
return 0
@staticmethod
def parse_float(v: Any) -> float:
try:
return float(v)
except ValueError:
return -1.0
@staticmethod
def parse_host_and_port(host_and_port: str, default_port: int = 0) -> Tuple[str, int]:
'''Parses a string into a tuple of its host and port. The port is 0 if not specified.'''
host = host_and_port
port = default_port
mx = re.match(r'^\[([^\]]+)\](?::(\d+))?$', host_and_port)
if mx is not None:
host = mx.group(1)
port_str = mx.group(2)
if port_str is not None:
port = int(port_str)
else:
s = host_and_port.split(':')
if len(s) == 2:
host = s[0]
if len(s[1]) > 0:
port = int(s[1])
return host, port
@staticmethod
def is_ipv6_address(address: str) -> bool:
'''Returns True if address is an IPv6 address, otherwise False.'''
is_ipv6 = True
try:
ipaddress.IPv6Address(address)
except ipaddress.AddressValueError:
is_ipv6 = False
return is_ipv6
@staticmethod
def is_windows() -> bool:
return sys.platform in ['win32', 'cygwin']

View File

@ -0,0 +1,151 @@
"""
The MIT License (MIT)
Copyright (C) 2017-2020 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
class VersionVulnerabilityDB: # pylint: disable=too-few-public-methods
# Format: [starting_vuln_version, last_vuln_version, affected, CVE_ID, CVSSv2, description]
# affected: 1 = server, 2 = client, 4 = local
# Example: if it affects servers, both remote & local, then affected
# = 1. If it affects servers, but is a local issue only,
# then affected = 1 + 4 = 5.
CVE: Dict[str, List[List[Any]]] = {
'Dropbear SSH': [
['0.0', '2018.76', 1, 'CVE-2018-15599', 5.0, 'remote users may enumerate users on the system'],
['0.0', '2017.74', 5, 'CVE-2017-9079', 4.7, 'local users can read certain files as root'],
['0.0', '2017.74', 5, 'CVE-2017-9078', 9.3, 'local users may elevate privileges to root under certain conditions'],
['0.0', '2016.73', 5, 'CVE-2016-7409', 2.1, 'local users can read process memory under limited conditions'],
['0.0', '2016.73', 1, 'CVE-2016-7408', 6.5, 'remote users can execute arbitrary code'],
['0.0', '2016.73', 5, 'CVE-2016-7407', 10.0, 'local users can execute arbitrary code'],
['0.0', '2016.73', 1, 'CVE-2016-7406', 10.0, 'remote users can execute arbitrary code'],
['0.44', '2015.71', 1, 'CVE-2016-3116', 5.5, 'bypass command restrictions via xauth command injection'],
['0.28', '2013.58', 1, 'CVE-2013-4434', 5.0, 'discover valid usernames through different time delays'],
['0.28', '2013.58', 1, 'CVE-2013-4421', 5.0, 'cause DoS via a compressed packet (memory consumption)'],
['0.52', '2011.54', 1, 'CVE-2012-0920', 7.1, 'execute arbitrary code or bypass command restrictions'],
['0.40', '0.48.1', 1, 'CVE-2007-1099', 7.5, 'conduct a MitM attack (no warning for hostkey mismatch)'],
['0.28', '0.47', 1, 'CVE-2006-1206', 7.5, 'cause DoS via large number of connections (slot exhaustion)'],
['0.39', '0.47', 1, 'CVE-2006-0225', 4.6, 'execute arbitrary commands via scp with crafted filenames'],
['0.28', '0.46', 1, 'CVE-2005-4178', 6.5, 'execute arbitrary code via buffer overflow vulnerability'],
['0.28', '0.42', 1, 'CVE-2004-2486', 7.5, 'execute arbitrary code via DSS verification code']],
'libssh': [
['0.6.4', '0.6.4', 1, 'CVE-2018-10933', 6.4, 'authentication bypass'],
['0.7.0', '0.7.5', 1, 'CVE-2018-10933', 6.4, 'authentication bypass'],
['0.8.0', '0.8.3', 1, 'CVE-2018-10933', 6.4, 'authentication bypass'],
['0.1', '0.7.2', 1, 'CVE-2016-0739', 4.3, 'conduct a MitM attack (weakness in DH key generation)'],
['0.5.1', '0.6.4', 1, 'CVE-2015-3146', 5.0, 'cause DoS via kex packets (null pointer dereference)'],
['0.5.1', '0.6.3', 1, 'CVE-2014-8132', 5.0, 'cause DoS via kex init packet (dangling pointer)'],
['0.4.7', '0.6.2', 1, 'CVE-2014-0017', 1.9, 'leak data via PRNG state reuse on forking servers'],
['0.4.7', '0.5.3', 1, 'CVE-2013-0176', 4.3, 'cause DoS via kex packet (null pointer dereference)'],
['0.4.7', '0.5.2', 1, 'CVE-2012-6063', 7.5, 'cause DoS or execute arbitrary code via sftp (double free)'],
['0.4.7', '0.5.2', 1, 'CVE-2012-4562', 7.5, 'cause DoS or execute arbitrary code (overflow check)'],
['0.4.7', '0.5.2', 1, 'CVE-2012-4561', 5.0, 'cause DoS via unspecified vectors (invalid pointer)'],
['0.4.7', '0.5.2', 1, 'CVE-2012-4560', 7.5, 'cause DoS or execute arbitrary code (buffer overflow)'],
['0.4.7', '0.5.2', 1, 'CVE-2012-4559', 6.8, 'cause DoS or execute arbitrary code (double free)']],
'OpenSSH': [
['1.0', '7.7', 1, 'CVE-2018-15473', 5.3, 'enumerate usernames due to timing discrepencies'],
['7.2', '7.2p2', 1, 'CVE-2016-6515', 7.8, 'cause DoS via long password string (crypt CPU consumption)'],
['1.2.2', '7.2', 1, 'CVE-2016-3115', 5.5, 'bypass command restrictions via crafted X11 forwarding data'],
['5.4', '7.1', 1, 'CVE-2016-1907', 5.0, 'cause DoS via crafted network traffic (out of bounds read)'],
['5.4', '7.1p1', 2, 'CVE-2016-0778', 4.6, 'cause DoS via requesting many forwardings (heap based buffer overflow)'],
['5.0', '7.1p1', 2, 'CVE-2016-0777', 4.0, 'leak data via allowing transfer of entire buffer'],
['6.0', '7.2p2', 5, 'CVE-2015-8325', 7.2, 'privilege escalation via triggering crafted environment'],
['6.8', '6.9', 5, 'CVE-2015-6565', 7.2, 'cause DoS via writing to a device (terminal disruption)'],
['5.0', '6.9', 5, 'CVE-2015-6564', 6.9, 'privilege escalation via leveraging sshd uid'],
['5.0', '6.9', 5, 'CVE-2015-6563', 1.9, 'conduct impersonation attack'],
['6.9p1', '6.9p1', 1, 'CVE-2015-5600', 8.5, 'cause Dos or aid in conduct brute force attack (CPU consumption)'],
['6.0', '6.6', 1, 'CVE-2015-5352', 4.3, 'bypass access restrictions via a specific connection'],
['6.0', '6.6', 2, 'CVE-2014-2653', 5.8, 'bypass SSHFP DNS RR check via unacceptable host certificate'],
['5.0', '6.5', 1, 'CVE-2014-2532', 5.8, 'bypass environment restrictions via specific string before wildcard'],
['1.2', '6.4', 1, 'CVE-2014-1692', 7.5, 'cause DoS via triggering error condition (memory corruption)'],
['6.2', '6.3', 1, 'CVE-2013-4548', 6.0, 'bypass command restrictions via crafted packet data'],
['1.2', '5.6', 1, 'CVE-2012-0814', 3.5, 'leak data via debug messages'],
['1.2', '5.8', 1, 'CVE-2011-5000', 3.5, 'cause DoS via large value in certain length field (memory consumption)'],
['5.6', '5.7', 2, 'CVE-2011-0539', 5.0, 'leak data or conduct hash collision attack'],
['1.2', '6.1', 1, 'CVE-2010-5107', 5.0, 'cause DoS via large number of connections (slot exhaustion)'],
['1.2', '5.8', 1, 'CVE-2010-4755', 4.0, 'cause DoS via crafted glob expression (CPU and memory consumption)'],
['1.2', '5.6', 1, 'CVE-2010-4478', 7.5, 'bypass authentication check via crafted values'],
['4.3', '4.8', 1, 'CVE-2009-2904', 6.9, 'privilege escalation via hard links to setuid programs'],
['4.0', '5.1', 1, 'CVE-2008-5161', 2.6, 'recover plaintext data from ciphertext'],
['1.2', '4.6', 1, 'CVE-2008-4109', 5.0, 'cause DoS via multiple login attempts (slot exhaustion)'],
['1.2', '4.8', 1, 'CVE-2008-1657', 6.5, 'bypass command restrictions via modifying session file'],
['1.2.2', '4.9', 1, 'CVE-2008-1483', 6.9, 'hijack forwarded X11 connections'],
['4.0', '4.6', 1, 'CVE-2007-4752', 7.5, 'privilege escalation via causing an X client to be trusted'],
['4.3p2', '4.3p2', 1, 'CVE-2007-3102', 4.3, 'allow attacker to write random data to audit log'],
['1.2', '4.6', 1, 'CVE-2007-2243', 5.0, 'discover valid usernames through different responses'],
['4.4', '4.4', 1, 'CVE-2006-5794', 7.5, 'bypass authentication'],
['4.1', '4.1p1', 1, 'CVE-2006-5229', 2.6, 'discover valid usernames through different time delays'],
['1.2', '4.3p2', 1, 'CVE-2006-5052', 5.0, 'discover valid usernames through different responses'],
['1.2', '4.3p2', 1, 'CVE-2006-5051', 9.3, 'cause DoS or execute arbitrary code (double free)'],
['4.5', '4.5', 1, 'CVE-2006-4925', 5.0, 'cause DoS via invalid protocol sequence (crash)'],
['1.2', '4.3p2', 1, 'CVE-2006-4924', 7.8, 'cause DoS via crafted packet (CPU consumption)'],
['3.8.1p1', '3.8.1p1', 1, 'CVE-2006-0883', 5.0, 'cause DoS via connecting multiple times (client connection refusal)'],
['3.0', '4.2p1', 1, 'CVE-2006-0225', 4.6, 'execute arbitrary code'],
['2.1', '4.1p1', 1, 'CVE-2005-2798', 5.0, 'leak data about authentication credentials'],
['3.5', '3.5p1', 1, 'CVE-2004-2760', 6.8, 'leak data through different connection states'],
['2.3', '3.7.1p2', 1, 'CVE-2004-2069', 5.0, 'cause DoS via large number of connections (slot exhaustion)'],
['3.0', '3.4p1', 1, 'CVE-2004-0175', 4.3, 'leak data through directoy traversal'],
['1.2', '3.9p1', 1, 'CVE-2003-1562', 7.6, 'leak data about authentication credentials'],
['3.1p1', '3.7.1p1', 1, 'CVE-2003-0787', 7.5, 'privilege escalation via modifying stack'],
['3.1p1', '3.7.1p1', 1, 'CVE-2003-0786', 10.0, 'privilege escalation via bypassing authentication'],
['1.0', '3.7.1', 1, 'CVE-2003-0695', 7.5, 'cause DoS or execute arbitrary code'],
['1.0', '3.7', 1, 'CVE-2003-0693', 10.0, 'execute arbitrary code'],
['3.0', '3.6.1p2', 1, 'CVE-2003-0386', 7.5, 'bypass address restrictions for connection'],
['3.1p1', '3.6.1p1', 1, 'CVE-2003-0190', 5.0, 'discover valid usernames through different time delays'],
['3.2.2', '3.2.2', 1, 'CVE-2002-0765', 7.5, 'bypass authentication'],
['1.2.2', '3.3p1', 1, 'CVE-2002-0640', 10.0, 'execute arbitrary code'],
['1.2.2', '3.3p1', 1, 'CVE-2002-0639', 10.0, 'execute arbitrary code'],
['2.1', '3.2', 1, 'CVE-2002-0575', 7.5, 'privilege escalation'],
['2.1', '3.0.2p1', 2, 'CVE-2002-0083', 10.0, 'privilege escalation'],
['3.0', '3.0p1', 1, 'CVE-2001-1507', 7.5, 'bypass authentication'],
['1.2.3', '3.0.1p1', 5, 'CVE-2001-0872', 7.2, 'privilege escalation via crafted environment variables'],
['1.2.3', '2.1.1', 1, 'CVE-2001-0361', 4.0, 'recover plaintext from ciphertext'],
['1.2', '2.1', 1, 'CVE-2000-0525', 10.0, 'execute arbitrary code (improper privileges)']],
'PuTTY': [
['0.54', '0.73', 2, 'CVE-2020-XXXX', 5.0, 'out of bounds memory read'],
['0.0', '0.72', 2, 'CVE-2019-17069', 5.0, 'potential DOS by remote SSHv1 server'],
['0.71', '0.72', 2, 'CVE-2019-17068', 5.0, 'xterm bracketed paste mode command injection'],
['0.52', '0.72', 2, 'CVE-2019-17067', 7.5, 'port rebinding weakness in port forward tunnel handling'],
['0.0', '0.71', 2, 'CVE-2019-XXXX', 5.0, 'undefined vulnerability in obsolete SSHv1 protocol handling'],
['0.0', '0.71', 6, 'CVE-2019-XXXX', 5.0, 'local privilege escalation in Pageant'],
['0.0', '0.70', 2, 'CVE-2019-9898', 7.5, 'potential recycling of random numbers'],
['0.0', '0.70', 2, 'CVE-2019-9897', 5.0, 'multiple denial-of-service issues from writing to the terminal'],
['0.0', '0.70', 6, 'CVE-2019-9896', 4.6, 'local application hijacking through malicious Windows help file'],
['0.0', '0.70', 2, 'CVE-2019-9894', 6.4, 'buffer overflow in RSA key exchange'],
['0.0', '0.69', 6, 'CVE-2016-6167', 4.4, 'local application hijacking through untrusted DLL loading'],
['0.0', '0.67', 2, 'CVE-2017-6542', 7.5, 'buffer overflow in UNIX client that can result in privilege escalation or denial-of-service'],
['0.0', '0.66', 2, 'CVE-2016-2563', 7.5, 'buffer overflow in SCP command-line utility'],
['0.0', '0.65', 2, 'CVE-2015-5309', 4.3, 'integer overflow in terminal-handling code'],
]
}
TXT: Dict[str, List[List[Any]]] = {
'Dropbear SSH': [
['0.28', '0.34', 1, 'remote root exploit', 'remote format string buffer overflow exploit (exploit-db#387)']],
'libssh': [
['0.3.3', '0.3.3', 1, 'null pointer check', 'missing null pointer check in "crypt_set_algorithms_server"'],
['0.3.3', '0.3.3', 1, 'integer overflow', 'integer overflow in "buffer_get_data"'],
['0.3.3', '0.3.3', 3, 'heap overflow', 'heap overflow in "packet_decrypt"']]
}

108
src/ssh_audit/writebuf.py Normal file
View File

@ -0,0 +1,108 @@
"""
The MIT License (MIT)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import io
import struct
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
class WriteBuf:
def __init__(self, data: Optional[bytes] = None) -> None:
super(WriteBuf, self).__init__()
self._wbuf = io.BytesIO(data) if data is not None else io.BytesIO()
def write(self, data: bytes) -> 'WriteBuf':
self._wbuf.write(data)
return self
def write_byte(self, v: int) -> 'WriteBuf':
return self.write(struct.pack('B', v))
def write_bool(self, v: bool) -> 'WriteBuf':
return self.write_byte(1 if v else 0)
def write_int(self, v: int) -> 'WriteBuf':
return self.write(struct.pack('>I', v))
def write_string(self, v: Union[bytes, str]) -> 'WriteBuf':
if not isinstance(v, bytes):
v = bytes(bytearray(v, 'utf-8'))
self.write_int(len(v))
return self.write(v)
def write_list(self, v: List[str]) -> 'WriteBuf':
return self.write_string(u','.join(v))
@classmethod
def _bitlength(cls, n: int) -> int:
try:
return n.bit_length()
except AttributeError:
return len(bin(n)) - (2 if n > 0 else 3)
@classmethod
def _create_mpint(cls, n: int, signed: bool = True, bits: Optional[int] = None) -> bytes:
if bits is None:
bits = cls._bitlength(n)
length = bits // 8 + (1 if n != 0 else 0)
ql = (length + 7) // 8
fmt, v2 = '>{}Q'.format(ql), [0] * ql
for i in range(ql):
v2[ql - i - 1] = n & 0xffffffffffffffff
n >>= 64
data = bytes(struct.pack(fmt, *v2)[-length:])
if not signed:
data = data.lstrip(b'\x00')
elif data.startswith(b'\xff\x80'):
data = data[1:]
return data
def write_mpint1(self, n: int) -> 'WriteBuf':
# NOTE: Data Type Enc @ http://www.snailbook.com/docs/protocol-1.5.txt
bits = self._bitlength(n)
data = self._create_mpint(n, False, bits)
self.write(struct.pack('>H', bits))
return self.write(data)
def write_mpint2(self, n: int) -> 'WriteBuf':
# NOTE: Section 5 @ https://www.ietf.org/rfc/rfc4251.txt
data = self._create_mpint(n)
return self.write_string(data)
def write_line(self, v: Union[bytes, str]) -> 'WriteBuf':
if not isinstance(v, bytes):
v = bytes(bytearray(v, 'utf-8'))
v += b'\r\n'
return self.write(v)
def write_flush(self) -> bytes:
payload = self._wbuf.getvalue()
self._wbuf.truncate(0)
self._wbuf.seek(0)
return payload
def reset(self) -> None:
self._wbuf = io.BytesIO()

240
ssh-audit.1 Normal file
View File

@ -0,0 +1,240 @@
.TH SSH-AUDIT 1 "February 7, 2021"
.SH NAME
\fBssh-audit\fP \- SSH server & client configuration auditor
.SH SYNOPSIS
.B ssh-audit
.RI [ options ] " <target_host>"
.SH DESCRIPTION
.PP
\fBssh-audit\fP analyzes the configuration of SSH servers & clients, then warns the user of weak, obsolete, and/or un-tested cryptographic primitives. It is very useful for hardening SSH tunnels, which by default tend to be optimized for compatibility, not security.
.PP
See <https://www.ssh\-audit.com/> for official hardening guides for common platforms.
.SH OPTIONS
.TP
.B -h, \-\-help
.br
Print short summary of options.
.TP
.B -1, \-\-ssh1
.br
Only perform an audit using SSH protocol version 1.
.TP
.B -2, \-\-ssh2
.br
Only perform an audit using SSH protocol version 2.
.TP
.B -4, \-\-ipv4
.br
Prioritize the usage of IPv4.
.TP
.B -6, \-\-ipv6
.br
Prioritize the usage of IPv6.
.TP
.B -b, \-\-batch
.br
Enables grepable output.
.TP
.B -c, \-\-client\-audit
.br
Starts a server on port 2222 to audit client software configuration. Use -p/--port=<port> to change port and -t/--timeout=<secs> to change listen timeout.
.TP
.B -j, \-\-json
.br
Output results in JSON format.
.TP
.B -l, \-\-level=<info|warn|fail>
.br
Specify the minimum output level. Default is info.
.TP
.B -L, \-\-list-policies
.br
List all official, built-in policies for common systems. Their full names can then be passed to -P/--policy.
.TP
.B \-\-lookup=<alg1,alg2,...>
.br
Look up the security information of an algorithm(s) in the internal database. Does not connect to a server.
.TP
.B -m, \-\-manual
.br
Print the man page (Windows only).
.TP
.B -M, \-\-make-policy=<custom_policy.txt>
.br
Creates a policy based on the target server. Useful when other servers should be compared to the target server's custom configuration (i.e.: a cluster environment). Note that the resulting policy can be edited manually.
.TP
.B -n, \-\-no-colors
.br
Disable color output.
.TP
.B -p, \-\-port=<port>
.br
The TCP port to connect to when auditing a server, or the port to listen on when auditing a client.
.TP
.B -P, \-\-policy=<"built-in policy name" | path/to/custom_policy.txt>
.br
Runs a policy audit against a target using the specified policy (see \fBPOLICY AUDIT\fP section for detailed description of this mode of operation). Combine with -c/--client-audit to audit a client configuration instead of a server. Use -L/--list-policies to list all official, built-in policies for common systems.
.TP
.B -t, \-\-timeout=<secs>
.br
The timeout, in seconds, for creating connections and reading data from the socket. Default is 5.
.TP
.B -T, \-\-targets=<hosts.txt>
.br
A file containing a list of target hosts. Each line must have one host, in the format of HOST[:PORT]. Use --threads to control concurrent scans.
.TP
.B \-\-threads=<threads>
.br
The number of threads to use when scanning multiple targets (with -T/--targets). Default is 32.
.TP
.B -v, \-\-verbose
.br
Enable verbose output.
.SH STANDARD AUDIT
.PP
By default, \fBssh-audit\fP performs a standard audit. That is, it enumerates all host key types, key exchanges, ciphers, MACs, and other information, then color-codes them in output to the user. Cryptographic primitives with potential issues are displayed in yellow; primitives with serious flaws are displayed in red.
.SH POLICY AUDIT
.PP
When the -P/--policy option is used, \fBssh-audit\fP performs a policy audit. The target's host key types, key exchanges, ciphers, MACs, and other information is compared to a set of expected values defined in the specified policy file. If everything matches, only a short message stating a passing result is reported. Otherwise, the field(s) that did not match are reported.
.PP
Policy auditing is helpful for ensuring a group of related servers are properly hardened to an exact specification.
.PP
The set of official built-in policies can be viewed with -L/--list-policies. Multiple servers can be audited with -T/--targets=<servers.txt>. Custom policies can be made from an ideal target server with -M/--make-policy=<custom_policy.txt>.
.SH EXAMPLES
.LP
Basic server auditing:
.RS
.nf
ssh-audit localhost
ssh-audit 127.0.0.1
ssh-audit 127.0.0.1:222
ssh-audit ::1
ssh-audit [::1]:222
.fi
.RE
.LP
To run a standard audit against many servers (place targets into servers.txt, one on each line in the format of HOST[:PORT]):
.RS
.nf
ssh-audit -T servers.txt
.fi
.RE
.LP
To audit a client configuration (listens on port 2222 by default; connect using "ssh -p 2222 anything@localhost"):
.RS
.nf
ssh-audit -c
.fi
.RE
.LP
To audit a client configuration, with a listener on port 4567:
.RS
.nf
ssh-audit -c -p 4567
.fi
.RE
.LP
To list all official built-in policies (hint: use their full names with -P/--policy):
.RS
.nf
ssh-audit -L
.fi
.RE
.LP
To run a built-in policy audit against a server (hint: use -L to see list of built-in policies):
.RS
.nf
ssh-audit -P "Hardened Ubuntu Server 20.04 LTS (version 1)" targetserver
.fi
.RE
.LP
To run a custom policy audit against a server (hint: use -M/--make-policy to create a custom policy file):
.RS
.nf
ssh-audit -P path/to/server_policy.txt targetserver
.fi
.RE
.LP
To run a policy audit against a client:
.RS
.nf
ssh-audit -c -P ["policy name" | path/to/client_policy.txt]
.fi
.RE
.LP
To run a policy audit against many servers:
.RS
.nf
ssh-audit -T servers.txt -P ["policy name" | path/to/server_policy.txt]
.fi
.RE
.LP
To create a policy based on a target server (which can be manually edited; see official built-in policies for syntax examples):
.RS
.nf
ssh-audit -M new_policy.txt targetserver
.fi
.RE
.SH RETURN VALUES
When a successful connection is made and all algorithms are rated as "good", \fBssh-audit\fP returns 0. Other possible return values are:
.RS
.nf
1 = connection error
2 = at least one algorithm warning was found
3 = at least one algorithm failure was found
<any other non-zero value> = unknown error
.fi
.RE
.SH SSH HARDENING GUIDES
Hardening guides for common platforms can be found at: <https://www.ssh\-audit.com/>
.SH BUG REPORTS
Please file bug reports as a Github Issue at: <https://github.com/jtesta/ssh\-audit/issues>
.SH AUTHOR
.LP
\fBssh-audit\fP was originally written by Andris Raugulis <moo@arthepsy.eu>, and maintained from 2015 to 2017.
.br
.LP
Maintainership was assumed and development was resumed in 2017 by Joe Testa <jtesta@positronsecurity.com>.

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +1,145 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pytest, os, sys, io
if sys.version_info[0] == 2:
import StringIO
StringIO = StringIO.StringIO
else:
StringIO = io.StringIO
import io
import sys
import socket
import pytest
@pytest.fixture(scope='module')
def ssh_audit():
__rdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
sys.path.append(os.path.abspath(__rdir))
return __import__('ssh-audit')
import ssh_audit.ssh_audit
return ssh_audit.ssh_audit
# pylint: disable=attribute-defined-outside-init
class _OutputSpy(list):
def begin(self):
self.__out = StringIO()
self.__old_stdout = sys.stdout
sys.stdout = self.__out
def flush(self):
lines = self.__out.getvalue().splitlines()
sys.stdout = self.__old_stdout
self.__out = None
return lines
def begin(self):
self.__out = io.StringIO()
self.__old_stdout = sys.stdout
sys.stdout = self.__out
def flush(self):
lines = self.__out.getvalue().splitlines()
sys.stdout = self.__old_stdout
self.__out = None
return lines
@pytest.fixture(scope='module')
def output_spy():
return _OutputSpy()
return _OutputSpy()
class _VirtualGlobalSocket:
def __init__(self, vsocket):
self.vsocket = vsocket
self.addrinfodata = {}
# pylint: disable=unused-argument
def create_connection(self, address, timeout=0, source_address=None):
# pylint: disable=protected-access
return self.vsocket._connect(address, True)
# pylint: disable=unused-argument
def socket(self,
family=socket.AF_INET,
socktype=socket.SOCK_STREAM,
proto=0,
fileno=None):
return self.vsocket
def getaddrinfo(self, host, port, family=0, socktype=0, proto=0, flags=0):
key = '{}#{}'.format(host, port)
if key in self.addrinfodata:
data = self.addrinfodata[key]
if isinstance(data, Exception):
raise data
return data
if host == 'localhost':
r = []
if family in (0, socket.AF_INET):
r.append((socket.AF_INET, 1, 6, '', ('127.0.0.1', port)))
if family in (0, socket.AF_INET6):
r.append((socket.AF_INET6, 1, 6, '', ('::1', port)))
return r
return []
class _VirtualSocket:
def __init__(self):
self.sock_address = ('127.0.0.1', 0)
self.peer_address = None
self._connected = False
self.timeout = -1.0
self.rdata = []
self.sdata = []
self.errors = {}
self.gsock = _VirtualGlobalSocket(self)
def _check_err(self, method):
method_error = self.errors.get(method)
if method_error:
raise method_error
def connect(self, address):
return self._connect(address, False)
def _connect(self, address, ret=True):
self.peer_address = address
self._connected = True
self._check_err('connect')
return self if ret else None
def settimeout(self, timeout):
self.timeout = timeout
def gettimeout(self):
return self.timeout
def getpeername(self):
if self.peer_address is None or not self._connected:
raise OSError(57, 'Socket is not connected')
return self.peer_address
def getsockname(self):
return self.sock_address
def bind(self, address):
self.sock_address = address
def listen(self, backlog):
pass
def accept(self):
# pylint: disable=protected-access
conn = _VirtualSocket()
conn.sock_address = self.sock_address
conn.peer_address = ('127.0.0.1', 0)
conn._connected = True
return conn, conn.peer_address
def recv(self, bufsize, flags=0):
# pylint: disable=unused-argument
if not self._connected:
raise OSError(54, 'Connection reset by peer')
if not len(self.rdata) > 0:
return b''
data = self.rdata.pop(0)
if isinstance(data, Exception):
raise data
return data
def send(self, data):
if self.peer_address is None or not self._connected:
raise OSError(32, 'Broken pipe')
self._check_err('send')
self.sdata.append(data)
@pytest.fixture()
def virtual_socket(monkeypatch):
vsocket = _VirtualSocket()
gsock = vsocket.gsock
monkeypatch.setattr(socket, 'create_connection', gsock.create_connection)
monkeypatch.setattr(socket, 'socket', gsock.socket)
monkeypatch.setattr(socket, 'getaddrinfo', gsock.getaddrinfo)
return vsocket

1
test/docker/.ed25519.sk Normal file
View File

@ -0,0 +1 @@
<EFBFBD><EFBFBD><EFBFBD><1C><><EFBFBD>V<EFBFBD><EFBFBD>Z/D<><<3C><>|S<>z<EFBFBD>=<3D>:<3A>1vu}<7D><><11>J<EFBFBD>ݷ<EFBFBD><DDB7>"<22>^Bb&U<><03>P<EFBFBD><50>

32
test/docker/Dockerfile Normal file
View File

@ -0,0 +1,32 @@
FROM ubuntu:16.04
COPY openssh-4.0p1/sshd /openssh/sshd-4.0p1
COPY openssh-5.6p1/sshd /openssh/sshd-5.6p1
COPY openssh-8.0p1/sshd /openssh/sshd-8.0p1
COPY dropbear-2019.78/dropbear /dropbear/dropbear-2019.78
COPY tinyssh-20190101/build/bin/tinysshd /tinysshd/tinyssh-20190101
# Dropbear host keys.
COPY dropbear_*_host_key* /etc/dropbear/
# OpenSSH configs.
COPY sshd_config* /etc/ssh/
# OpenSSH host keys & moduli file.
COPY ssh_host_* /etc/ssh/
COPY ssh1_host_* /etc/ssh/
COPY moduli_1024 /usr/local/etc/moduli
# TinySSH host keys.
COPY ed25519.pk /etc/tinyssh/
COPY .ed25519.sk /etc/tinyssh/
COPY debug.sh /debug.sh
RUN apt update 2> /dev/null
RUN apt install -y libssl-dev strace rsyslog ucspi-tcp 2> /dev/null
RUN apt clean 2> /dev/null
RUN useradd -s /bin/false sshd
RUN mkdir /var/empty
EXPOSE 22

9
test/docker/debug.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
# This script is run on in docker container. It will enable logging for sshd in
# /var/log/auth.log.
/etc/init.d/rsyslog start
sleep 1
/openssh/sshd-5.6p1 -o LogLevel=DEBUG3 -f /etc/ssh/sshd_config-5.6p1_test1
/bin/bash

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
test/docker/ed25519.pk Normal file
View File

@ -0,0 +1 @@
1vu}<7D><><11>J<EFBFBD>ݷ<EFBFBD><DDB7>"<22>^Bb&U<><03>P<EFBFBD><50>

View File

@ -0,0 +1 @@
{"banner": {"comments": null, "protocol": [2, 0], "raw": "SSH-2.0-dropbear_2019.78", "software": "dropbear_2019.78"}, "compression": ["zlib@openssh.com", "none"], "enc": ["aes128-ctr", "aes256-ctr", "aes128-cbc", "aes256-cbc", "3des-ctr", "3des-cbc"], "fingerprints": [{"fp": "SHA256:CDfAU12pjQS7/91kg7gYacza0U/6PDbE04Ic3IpYxkM", "type": "ssh-rsa"}], "kex": [{"algorithm": "curve25519-sha256"}, {"algorithm": "curve25519-sha256@libssh.org"}, {"algorithm": "ecdh-sha2-nistp521"}, {"algorithm": "ecdh-sha2-nistp384"}, {"algorithm": "ecdh-sha2-nistp256"}, {"algorithm": "diffie-hellman-group14-sha256"}, {"algorithm": "diffie-hellman-group14-sha1"}, {"algorithm": "kexguess2@matt.ucc.asn.au"}], "key": [{"algorithm": "ecdsa-sha2-nistp256"}, {"algorithm": "ssh-rsa", "keysize": 1024}, {"algorithm": "ssh-dss"}], "mac": ["hmac-sha1-96", "hmac-sha1", "hmac-sha2-256"], "target": "localhost"}

View File

@ -0,0 +1,88 @@
# general
(gen) banner: SSH-2.0-dropbear_2019.78
(gen) software: Dropbear SSH 2019.78
(gen) compatibility: OpenSSH 7.4+ (some functionality from 6.6), Dropbear SSH 2018.76+
(gen) compression: enabled (zlib@openssh.com)
# key exchange algorithms
(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76
(kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.5, Dropbear SSH 2013.62
(kex) ecdh-sha2-nistp521 -- [fail] using weak elliptic curves
`- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62
(kex) ecdh-sha2-nistp384 -- [fail] using weak elliptic curves
`- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62
(kex) ecdh-sha2-nistp256 -- [fail] using weak elliptic curves
`- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62
(kex) diffie-hellman-group14-sha256 -- [info] available since OpenSSH 7.3, Dropbear SSH 2016.73
(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
(kex) kexguess2@matt.ucc.asn.au -- [info] available since Dropbear SSH 2013.57
# host-key algorithms
(key) ecdsa-sha2-nistp256 -- [fail] using weak elliptic curves
 `- [warn] using weak random number generator could reveal the key
`- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62
(key) ssh-rsa (1024-bit) -- [fail] using weak hashing algorithm
 `- [fail] using small 1024-bit modulus
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28
`- [info] a future deprecation notice has been issued in OpenSSH 8.2: https://www.openssh.com/txt/release-8.2
(key) ssh-dss -- [fail] using small 1024-bit modulus
 `- [fail] removed (in server) and disabled (in client) since OpenSSH 7.0, weak algorithm
 `- [warn] using weak random number generator could reveal the key
`- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28
# encryption algorithms (ciphers)
(enc) aes128-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52
(enc) aes256-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52
(enc) aes128-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
(enc) aes256-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.47
(enc) 3des-ctr -- [fail] using weak cipher
`- [info] available since Dropbear SSH 0.52
(enc) 3des-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.4, unsafe algorithm
 `- [warn] using weak cipher
 `- [warn] using weak cipher mode
 `- [warn] using small 64-bit block size
`- [info] available since OpenSSH 1.2.2, Dropbear SSH 0.28
# message authentication code algorithms
(mac) hmac-sha1-96 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.47
(mac) hmac-sha1 -- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28
(mac) hmac-sha2-256 -- [warn] using encrypt-and-MAC mode
`- [info] available since OpenSSH 5.9, Dropbear SSH 2013.56
# fingerprints
(fin) ssh-rsa: SHA256:CDfAU12pjQS7/91kg7gYacza0U/6PDbE04Ic3IpYxkM
# algorithm recommendations (for Dropbear SSH 2019.78)
(rec) -3des-cbc -- enc algorithm to remove 
(rec) -3des-ctr -- enc algorithm to remove 
(rec) -aes128-cbc -- enc algorithm to remove 
(rec) -aes256-cbc -- enc algorithm to remove 
(rec) -ecdh-sha2-nistp256 -- kex algorithm to remove 
(rec) -ecdh-sha2-nistp384 -- kex algorithm to remove 
(rec) -ecdh-sha2-nistp521 -- kex algorithm to remove 
(rec) -ecdsa-sha2-nistp256 -- key algorithm to remove 
(rec) -hmac-sha1-96 -- mac algorithm to remove 
(rec) -ssh-dss -- key algorithm to remove 
(rec) -ssh-rsa -- key algorithm to remove 
(rec) +diffie-hellman-group16-sha512 -- kex algorithm to append 
(rec) +twofish128-ctr -- enc algorithm to append 
(rec) +twofish256-ctr -- enc algorithm to append 
(rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove 
(rec) -hmac-sha1 -- mac algorithm to remove 
(rec) -hmac-sha2-256 -- mac algorithm to remove 
# additional info
(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html>

View File

@ -0,0 +1 @@
{"banner": {"comments": null, "protocol": [1, 99], "raw": "SSH-1.99-OpenSSH_4.0", "software": "OpenSSH_4.0"}, "compression": ["none", "zlib"], "enc": ["aes128-cbc", "3des-cbc", "blowfish-cbc", "cast128-cbc", "arcfour", "aes192-cbc", "aes256-cbc", "rijndael-cbc@lysator.liu.se", "aes128-ctr", "aes192-ctr", "aes256-ctr"], "fingerprints": [{"fp": "SHA256:YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4", "type": "ssh-rsa"}], "kex": [{"algorithm": "diffie-hellman-group-exchange-sha1", "keysize": 1024}, {"algorithm": "diffie-hellman-group14-sha1"}, {"algorithm": "diffie-hellman-group1-sha1"}], "key": [{"algorithm": "ssh-rsa", "keysize": 1024}, {"algorithm": "ssh-dss"}], "mac": ["hmac-md5", "hmac-sha1", "hmac-ripemd160", "hmac-ripemd160@openssh.com", "hmac-sha1-96", "hmac-md5-96"], "target": "localhost"}

View File

@ -0,0 +1,145 @@
# general
(gen) banner: SSH-1.99-OpenSSH_4.0
(gen) protocol SSH1 enabled
(gen) software: OpenSSH 4.0
(gen) compatibility: OpenSSH 3.9-6.6, Dropbear SSH 0.53+ (some functionality from 0.52)
(gen) compression: enabled (zlib)
# security
(cve) CVE-2018-15473 -- (CVSSv2: 5.3) enumerate usernames due to timing discrepencies
(cve) CVE-2016-3115 -- (CVSSv2: 5.5) bypass command restrictions via crafted X11 forwarding data
(cve) CVE-2014-1692 -- (CVSSv2: 7.5) cause DoS via triggering error condition (memory corruption)
(cve) CVE-2012-0814 -- (CVSSv2: 3.5) leak data via debug messages
(cve) CVE-2011-5000 -- (CVSSv2: 3.5) cause DoS via large value in certain length field (memory consumption)
(cve) CVE-2010-5107 -- (CVSSv2: 5.0) cause DoS via large number of connections (slot exhaustion)
(cve) CVE-2010-4755 -- (CVSSv2: 4.0) cause DoS via crafted glob expression (CPU and memory consumption)
(cve) CVE-2010-4478 -- (CVSSv2: 7.5) bypass authentication check via crafted values
(cve) CVE-2008-5161 -- (CVSSv2: 2.6) recover plaintext data from ciphertext
(cve) CVE-2008-4109 -- (CVSSv2: 5.0) cause DoS via multiple login attempts (slot exhaustion)
(cve) CVE-2008-1657 -- (CVSSv2: 6.5) bypass command restrictions via modifying session file
(cve) CVE-2008-1483 -- (CVSSv2: 6.9) hijack forwarded X11 connections
(cve) CVE-2007-4752 -- (CVSSv2: 7.5) privilege escalation via causing an X client to be trusted
(cve) CVE-2007-2243 -- (CVSSv2: 5.0) discover valid usernames through different responses
(cve) CVE-2006-5052 -- (CVSSv2: 5.0) discover valid usernames through different responses
(cve) CVE-2006-5051 -- (CVSSv2: 9.3) cause DoS or execute arbitrary code (double free)
(cve) CVE-2006-4924 -- (CVSSv2: 7.8) cause DoS via crafted packet (CPU consumption)
(cve) CVE-2006-0225 -- (CVSSv2: 4.6) execute arbitrary code
(cve) CVE-2005-2798 -- (CVSSv2: 5.0) leak data about authentication credentials
(sec) SSH v1 enabled -- SSH v1 can be exploited to recover plaintext passwords
# key exchange algorithms
(kex) diffie-hellman-group-exchange-sha1 (1024-bit) -- [fail] using small 1024-bit modulus
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.3.0
(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
(kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus
 `- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [fail] disabled (in client) since OpenSSH 7.0, logjam attack
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
# host-key algorithms
(key) ssh-rsa (1024-bit) -- [fail] using weak hashing algorithm
 `- [fail] using small 1024-bit modulus
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28
`- [info] a future deprecation notice has been issued in OpenSSH 8.2: https://www.openssh.com/txt/release-8.2
(key) ssh-dss -- [fail] using small 1024-bit modulus
 `- [fail] removed (in server) and disabled (in client) since OpenSSH 7.0, weak algorithm
 `- [warn] using weak random number generator could reveal the key
`- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28
# encryption algorithms (ciphers)
(enc) aes128-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
(enc) 3des-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.4, unsafe algorithm
 `- [warn] using weak cipher
 `- [warn] using weak cipher mode
 `- [warn] using small 64-bit block size
`- [info] available since OpenSSH 1.2.2, Dropbear SSH 0.28
(enc) blowfish-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [fail] disabled since Dropbear SSH 0.53
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher mode
 `- [warn] using small 64-bit block size
`- [info] available since OpenSSH 1.2.2, Dropbear SSH 0.28
(enc) cast128-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher mode
 `- [warn] using small 64-bit block size
`- [info] available since OpenSSH 2.1.0
(enc) arcfour -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher
`- [info] available since OpenSSH 2.1.0
(enc) aes192-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0
(enc) aes256-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.47
(enc) rijndael-cbc@lysator.liu.se -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0
(enc) aes128-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52
(enc) aes192-ctr -- [info] available since OpenSSH 3.7
(enc) aes256-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52
# message authentication code algorithms
(mac) hmac-md5 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28
(mac) hmac-sha1 -- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28
(mac) hmac-ripemd160 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
`- [info] available since OpenSSH 2.5.0
(mac) hmac-ripemd160@openssh.com -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
`- [info] available since OpenSSH 2.1.0
(mac) hmac-sha1-96 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.47
(mac) hmac-md5-96 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.5.0
# fingerprints
(fin) ssh-rsa: SHA256:YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4
# algorithm recommendations (for OpenSSH 4.0)
(rec) -3des-cbc -- enc algorithm to remove 
(rec) -aes128-cbc -- enc algorithm to remove 
(rec) -aes192-cbc -- enc algorithm to remove 
(rec) -aes256-cbc -- enc algorithm to remove 
(rec) -arcfour -- enc algorithm to remove 
(rec) -blowfish-cbc -- enc algorithm to remove 
(rec) -cast128-cbc -- enc algorithm to remove 
(rec) -diffie-hellman-group-exchange-sha1 -- kex algorithm to remove 
(rec) -diffie-hellman-group1-sha1 -- kex algorithm to remove 
(rec) -hmac-md5 -- mac algorithm to remove 
(rec) -hmac-md5-96 -- mac algorithm to remove 
(rec) -hmac-ripemd160 -- mac algorithm to remove 
(rec) -hmac-ripemd160@openssh.com -- mac algorithm to remove 
(rec) -hmac-sha1-96 -- mac algorithm to remove 
(rec) -rijndael-cbc@lysator.liu.se -- enc algorithm to remove 
(rec) -ssh-dss -- key algorithm to remove 
(rec) -ssh-rsa -- key algorithm to remove 
(rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove 
(rec) -hmac-sha1 -- mac algorithm to remove 
# additional info
(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html>

View File

@ -0,0 +1 @@
{"errors": [], "host": "localhost", "passed": true, "policy": "Docker policy: test1 (version 1)"}

View File

@ -0,0 +1,3 @@
Host: localhost:2222
Policy: Docker policy: test1 (version 1)
Result: ✔ Passed

View File

@ -0,0 +1 @@
{"errors": [{"actual": ["3072"], "expected_optional": [""], "expected_required": ["4096"], "mismatched_field": "RSA host key (ssh-rsa-cert-v01@openssh.com) sizes"}, {"actual": ["1024"], "expected_optional": [""], "expected_required": ["4096"], "mismatched_field": "RSA CA key (ssh-rsa-cert-v01@openssh.com) sizes"}], "host": "localhost", "passed": false, "policy": "Docker poliicy: test10 (version 1)"}

View File

@ -0,0 +1,13 @@
Host: localhost:2222
Policy: Docker poliicy: test10 (version 1)
Result: ❌ Failed!

Errors:
* RSA CA key (ssh-rsa-cert-v01@openssh.com) sizes did not match.
- Expected: 4096
- Actual: 1024
* RSA host key (ssh-rsa-cert-v01@openssh.com) sizes did not match.
- Expected: 4096
- Actual: 3072


View File

@ -0,0 +1 @@
{"errors": [{"actual": ["diffie-hellman-group-exchange-sha256", "diffie-hellman-group-exchange-sha1", "diffie-hellman-group14-sha1", "diffie-hellman-group1-sha1"], "expected_optional": [""], "expected_required": ["kex_alg1", "kex_alg2"], "mismatched_field": "Key exchanges"}], "host": "localhost", "passed": false, "policy": "Docker policy: test2 (version 1)"}

View File

@ -0,0 +1,9 @@
Host: localhost:2222
Policy: Docker policy: test2 (version 1)
Result: ❌ Failed!

Errors:
* Key exchanges did not match.
- Expected: kex_alg1, kex_alg2
- Actual: diffie-hellman-group-exchange-sha256, diffie-hellman-group-exchange-sha1, diffie-hellman-group14-sha1, diffie-hellman-group1-sha1


View File

@ -0,0 +1 @@
{"errors": [{"actual": ["ssh-rsa", "ssh-dss"], "expected_optional": [""], "expected_required": ["ssh-rsa", "ssh-dss", "key_alg1"], "mismatched_field": "Host keys"}], "host": "localhost", "passed": false, "policy": "Docker policy: test3 (version 1)"}

View File

@ -0,0 +1,9 @@
Host: localhost:2222
Policy: Docker policy: test3 (version 1)
Result: ❌ Failed!

Errors:
* Host keys did not match.
- Expected: ssh-rsa, ssh-dss, key_alg1
- Actual: ssh-rsa, ssh-dss


View File

@ -0,0 +1 @@
{"errors": [{"actual": ["aes128-ctr", "aes192-ctr", "aes256-ctr", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "blowfish-cbc", "cast128-cbc", "aes192-cbc", "aes256-cbc", "arcfour", "rijndael-cbc@lysator.liu.se"], "expected_optional": [""], "expected_required": ["cipher_alg1", "cipher_alg2"], "mismatched_field": "Ciphers"}], "host": "localhost", "passed": false, "policy": "Docker policy: test4 (version 1)"}

View File

@ -0,0 +1,9 @@
Host: localhost:2222
Policy: Docker policy: test4 (version 1)
Result: ❌ Failed!

Errors:
* Ciphers did not match.
- Expected: cipher_alg1, cipher_alg2
- Actual: aes128-ctr, aes192-ctr, aes256-ctr, arcfour256, arcfour128, aes128-cbc, 3des-cbc, blowfish-cbc, cast128-cbc, aes192-cbc, aes256-cbc, arcfour, rijndael-cbc@lysator.liu.se


View File

@ -0,0 +1 @@
{"errors": [{"actual": ["hmac-md5", "hmac-sha1", "umac-64@openssh.com", "hmac-ripemd160", "hmac-ripemd160@openssh.com", "hmac-sha1-96", "hmac-md5-96"], "expected_optional": [""], "expected_required": ["hmac-md5", "hmac-sha1", "umac-64@openssh.com", "hmac-ripemd160", "hmac-ripemd160@openssh.com", "hmac_alg1", "hmac-md5-96"], "mismatched_field": "MACs"}], "host": "localhost", "passed": false, "policy": "Docker policy: test5 (version 1)"}

View File

@ -0,0 +1,9 @@
Host: localhost:2222
Policy: Docker policy: test5 (version 1)
Result: ❌ Failed!

Errors:
* MACs did not match.
- Expected: hmac-md5, hmac-sha1, umac-64@openssh.com, hmac-ripemd160, hmac-ripemd160@openssh.com, hmac_alg1, hmac-md5-96
- Actual: hmac-md5, hmac-sha1, umac-64@openssh.com, hmac-ripemd160, hmac-ripemd160@openssh.com, hmac-sha1-96, hmac-md5-96


View File

@ -0,0 +1 @@
{"errors": [], "host": "localhost", "passed": true, "policy": "Docker poliicy: test7 (version 1)"}

View File

@ -0,0 +1,3 @@
Host: localhost:2222
Policy: Docker poliicy: test7 (version 1)
Result: ✔ Passed

View File

@ -0,0 +1 @@
{"errors": [{"actual": ["1024"], "expected_optional": [""], "expected_required": ["2048"], "mismatched_field": "RSA CA key (ssh-rsa-cert-v01@openssh.com) sizes"}], "host": "localhost", "passed": false, "policy": "Docker poliicy: test8 (version 1)"}

View File

@ -0,0 +1,9 @@
Host: localhost:2222
Policy: Docker poliicy: test8 (version 1)
Result: ❌ Failed!

Errors:
* RSA CA key (ssh-rsa-cert-v01@openssh.com) sizes did not match.
- Expected: 2048
- Actual: 1024


View File

@ -0,0 +1 @@
{"errors": [{"actual": ["3072"], "expected_optional": [""], "expected_required": ["4096"], "mismatched_field": "RSA host key (ssh-rsa-cert-v01@openssh.com) sizes"}], "host": "localhost", "passed": false, "policy": "Docker poliicy: test9 (version 1)"}

View File

@ -0,0 +1,9 @@
Host: localhost:2222
Policy: Docker poliicy: test9 (version 1)
Result: ❌ Failed!

Errors:
* RSA host key (ssh-rsa-cert-v01@openssh.com) sizes did not match.
- Expected: 4096
- Actual: 3072


View File

@ -0,0 +1 @@
{"banner": {"comments": null, "protocol": [2, 0], "raw": "SSH-2.0-OpenSSH_5.6", "software": "OpenSSH_5.6"}, "compression": ["none", "zlib@openssh.com"], "enc": ["aes128-ctr", "aes192-ctr", "aes256-ctr", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "blowfish-cbc", "cast128-cbc", "aes192-cbc", "aes256-cbc", "arcfour", "rijndael-cbc@lysator.liu.se"], "fingerprints": [{"fp": "SHA256:YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4", "type": "ssh-rsa"}], "kex": [{"algorithm": "diffie-hellman-group-exchange-sha256", "keysize": 1024}, {"algorithm": "diffie-hellman-group-exchange-sha1", "keysize": 1024}, {"algorithm": "diffie-hellman-group14-sha1"}, {"algorithm": "diffie-hellman-group1-sha1"}], "key": [{"algorithm": "ssh-rsa", "keysize": 1024}, {"algorithm": "ssh-dss"}], "mac": ["hmac-md5", "hmac-sha1", "umac-64@openssh.com", "hmac-ripemd160", "hmac-ripemd160@openssh.com", "hmac-sha1-96", "hmac-md5-96"], "target": "localhost"}

View File

@ -0,0 +1,153 @@
# general
(gen) banner: SSH-2.0-OpenSSH_5.6
(gen) software: OpenSSH 5.6
(gen) compatibility: OpenSSH 4.7-6.6, Dropbear SSH 0.53+ (some functionality from 0.52)
(gen) compression: enabled (zlib@openssh.com)
# security
(cve) CVE-2018-15473 -- (CVSSv2: 5.3) enumerate usernames due to timing discrepencies
(cve) CVE-2016-3115 -- (CVSSv2: 5.5) bypass command restrictions via crafted X11 forwarding data
(cve) CVE-2016-1907 -- (CVSSv2: 5.0) cause DoS via crafted network traffic (out of bounds read)
(cve) CVE-2015-6564 -- (CVSSv2: 6.9) privilege escalation via leveraging sshd uid
(cve) CVE-2015-6563 -- (CVSSv2: 1.9) conduct impersonation attack
(cve) CVE-2014-2532 -- (CVSSv2: 5.8) bypass environment restrictions via specific string before wildcard
(cve) CVE-2014-1692 -- (CVSSv2: 7.5) cause DoS via triggering error condition (memory corruption)
(cve) CVE-2012-0814 -- (CVSSv2: 3.5) leak data via debug messages
(cve) CVE-2011-5000 -- (CVSSv2: 3.5) cause DoS via large value in certain length field (memory consumption)
(cve) CVE-2010-5107 -- (CVSSv2: 5.0) cause DoS via large number of connections (slot exhaustion)
(cve) CVE-2010-4755 -- (CVSSv2: 4.0) cause DoS via crafted glob expression (CPU and memory consumption)
(cve) CVE-2010-4478 -- (CVSSv2: 7.5) bypass authentication check via crafted values
# key exchange algorithms
(kex) diffie-hellman-group-exchange-sha256 (1024-bit) -- [fail] using small 1024-bit modulus
`- [info] available since OpenSSH 4.4
(kex) diffie-hellman-group-exchange-sha1 (1024-bit) -- [fail] using small 1024-bit modulus
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.3.0
(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
(kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus
 `- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [fail] disabled (in client) since OpenSSH 7.0, logjam attack
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
# host-key algorithms
(key) ssh-rsa (1024-bit) -- [fail] using weak hashing algorithm
 `- [fail] using small 1024-bit modulus
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28
`- [info] a future deprecation notice has been issued in OpenSSH 8.2: https://www.openssh.com/txt/release-8.2
(key) ssh-dss -- [fail] using small 1024-bit modulus
 `- [fail] removed (in server) and disabled (in client) since OpenSSH 7.0, weak algorithm
 `- [warn] using weak random number generator could reveal the key
`- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28
# encryption algorithms (ciphers)
(enc) aes128-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52
(enc) aes192-ctr -- [info] available since OpenSSH 3.7
(enc) aes256-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52
(enc) arcfour256 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher
`- [info] available since OpenSSH 4.2
(enc) arcfour128 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher
`- [info] available since OpenSSH 4.2
(enc) aes128-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
(enc) 3des-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.4, unsafe algorithm
 `- [warn] using weak cipher
 `- [warn] using weak cipher mode
 `- [warn] using small 64-bit block size
`- [info] available since OpenSSH 1.2.2, Dropbear SSH 0.28
(enc) blowfish-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [fail] disabled since Dropbear SSH 0.53
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher mode
 `- [warn] using small 64-bit block size
`- [info] available since OpenSSH 1.2.2, Dropbear SSH 0.28
(enc) cast128-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher mode
 `- [warn] using small 64-bit block size
`- [info] available since OpenSSH 2.1.0
(enc) aes192-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0
(enc) aes256-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.47
(enc) arcfour -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher
`- [info] available since OpenSSH 2.1.0
(enc) rijndael-cbc@lysator.liu.se -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0
# message authentication code algorithms
(mac) hmac-md5 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28
(mac) hmac-sha1 -- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28
(mac) umac-64@openssh.com -- [warn] using encrypt-and-MAC mode
 `- [warn] using small 64-bit tag size
`- [info] available since OpenSSH 4.7
(mac) hmac-ripemd160 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
`- [info] available since OpenSSH 2.5.0
(mac) hmac-ripemd160@openssh.com -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
`- [info] available since OpenSSH 2.1.0
(mac) hmac-sha1-96 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.47
(mac) hmac-md5-96 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.5.0
# fingerprints
(fin) ssh-rsa: SHA256:YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4
# algorithm recommendations (for OpenSSH 5.6)
(rec) !diffie-hellman-group-exchange-sha256 -- kex algorithm to change (increase modulus size to 2048 bits or larger) 
(rec) -3des-cbc -- enc algorithm to remove 
(rec) -aes128-cbc -- enc algorithm to remove 
(rec) -aes192-cbc -- enc algorithm to remove 
(rec) -aes256-cbc -- enc algorithm to remove 
(rec) -arcfour -- enc algorithm to remove 
(rec) -arcfour128 -- enc algorithm to remove 
(rec) -arcfour256 -- enc algorithm to remove 
(rec) -blowfish-cbc -- enc algorithm to remove 
(rec) -cast128-cbc -- enc algorithm to remove 
(rec) -diffie-hellman-group-exchange-sha1 -- kex algorithm to remove 
(rec) -diffie-hellman-group1-sha1 -- kex algorithm to remove 
(rec) -hmac-md5 -- mac algorithm to remove 
(rec) -hmac-md5-96 -- mac algorithm to remove 
(rec) -hmac-ripemd160 -- mac algorithm to remove 
(rec) -hmac-ripemd160@openssh.com -- mac algorithm to remove 
(rec) -hmac-sha1-96 -- mac algorithm to remove 
(rec) -rijndael-cbc@lysator.liu.se -- enc algorithm to remove 
(rec) -ssh-dss -- key algorithm to remove 
(rec) -ssh-rsa -- key algorithm to remove 
(rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove 
(rec) -hmac-sha1 -- mac algorithm to remove 
(rec) -umac-64@openssh.com -- mac algorithm to remove 
# additional info
(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html>

View File

@ -0,0 +1 @@
{"banner": {"comments": null, "protocol": [2, 0], "raw": "SSH-2.0-OpenSSH_5.6", "software": "OpenSSH_5.6"}, "compression": ["none", "zlib@openssh.com"], "enc": ["aes128-ctr", "aes192-ctr", "aes256-ctr", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "blowfish-cbc", "cast128-cbc", "aes192-cbc", "aes256-cbc", "arcfour", "rijndael-cbc@lysator.liu.se"], "fingerprints": [{"fp": "SHA256:YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4", "type": "ssh-rsa"}], "kex": [{"algorithm": "diffie-hellman-group-exchange-sha256", "keysize": 1024}, {"algorithm": "diffie-hellman-group-exchange-sha1", "keysize": 1024}, {"algorithm": "diffie-hellman-group14-sha1"}, {"algorithm": "diffie-hellman-group1-sha1"}], "key": [{"algorithm": "ssh-rsa", "keysize": 1024}, {"algorithm": "ssh-rsa-cert-v01@openssh.com", "casize": 1024, "keysize": 1024}], "mac": ["hmac-md5", "hmac-sha1", "umac-64@openssh.com", "hmac-ripemd160", "hmac-ripemd160@openssh.com", "hmac-sha1-96", "hmac-md5-96"], "target": "localhost"}

View File

@ -0,0 +1,153 @@
# general
(gen) banner: SSH-2.0-OpenSSH_5.6
(gen) software: OpenSSH 5.6
(gen) compatibility: OpenSSH 5.6-6.6, Dropbear SSH 0.53+ (some functionality from 0.52)
(gen) compression: enabled (zlib@openssh.com)
# security
(cve) CVE-2018-15473 -- (CVSSv2: 5.3) enumerate usernames due to timing discrepencies
(cve) CVE-2016-3115 -- (CVSSv2: 5.5) bypass command restrictions via crafted X11 forwarding data
(cve) CVE-2016-1907 -- (CVSSv2: 5.0) cause DoS via crafted network traffic (out of bounds read)
(cve) CVE-2015-6564 -- (CVSSv2: 6.9) privilege escalation via leveraging sshd uid
(cve) CVE-2015-6563 -- (CVSSv2: 1.9) conduct impersonation attack
(cve) CVE-2014-2532 -- (CVSSv2: 5.8) bypass environment restrictions via specific string before wildcard
(cve) CVE-2014-1692 -- (CVSSv2: 7.5) cause DoS via triggering error condition (memory corruption)
(cve) CVE-2012-0814 -- (CVSSv2: 3.5) leak data via debug messages
(cve) CVE-2011-5000 -- (CVSSv2: 3.5) cause DoS via large value in certain length field (memory consumption)
(cve) CVE-2010-5107 -- (CVSSv2: 5.0) cause DoS via large number of connections (slot exhaustion)
(cve) CVE-2010-4755 -- (CVSSv2: 4.0) cause DoS via crafted glob expression (CPU and memory consumption)
(cve) CVE-2010-4478 -- (CVSSv2: 7.5) bypass authentication check via crafted values
# key exchange algorithms
(kex) diffie-hellman-group-exchange-sha256 (1024-bit) -- [fail] using small 1024-bit modulus
`- [info] available since OpenSSH 4.4
(kex) diffie-hellman-group-exchange-sha1 (1024-bit) -- [fail] using small 1024-bit modulus
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.3.0
(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
(kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus
 `- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [fail] disabled (in client) since OpenSSH 7.0, logjam attack
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
# host-key algorithms
(key) ssh-rsa (1024-bit) -- [fail] using weak hashing algorithm
 `- [fail] using small 1024-bit modulus
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28
`- [info] a future deprecation notice has been issued in OpenSSH 8.2: https://www.openssh.com/txt/release-8.2
(key) ssh-rsa-cert-v01@openssh.com (1024-bit cert/1024-bit CA) -- [fail] using weak hashing algorithm
 `- [fail] using small 1024-bit modulus
`- [info] available since OpenSSH 5.6
`- [info] a future deprecation notice has been issued in OpenSSH 8.2: https://www.openssh.com/txt/release-8.2
# encryption algorithms (ciphers)
(enc) aes128-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52
(enc) aes192-ctr -- [info] available since OpenSSH 3.7
(enc) aes256-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52
(enc) arcfour256 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher
`- [info] available since OpenSSH 4.2
(enc) arcfour128 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher
`- [info] available since OpenSSH 4.2
(enc) aes128-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
(enc) 3des-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.4, unsafe algorithm
 `- [warn] using weak cipher
 `- [warn] using weak cipher mode
 `- [warn] using small 64-bit block size
`- [info] available since OpenSSH 1.2.2, Dropbear SSH 0.28
(enc) blowfish-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [fail] disabled since Dropbear SSH 0.53
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher mode
 `- [warn] using small 64-bit block size
`- [info] available since OpenSSH 1.2.2, Dropbear SSH 0.28
(enc) cast128-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher mode
 `- [warn] using small 64-bit block size
`- [info] available since OpenSSH 2.1.0
(enc) aes192-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0
(enc) aes256-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.47
(enc) arcfour -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher
`- [info] available since OpenSSH 2.1.0
(enc) rijndael-cbc@lysator.liu.se -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0
# message authentication code algorithms
(mac) hmac-md5 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28
(mac) hmac-sha1 -- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28
(mac) umac-64@openssh.com -- [warn] using encrypt-and-MAC mode
 `- [warn] using small 64-bit tag size
`- [info] available since OpenSSH 4.7
(mac) hmac-ripemd160 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
`- [info] available since OpenSSH 2.5.0
(mac) hmac-ripemd160@openssh.com -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
`- [info] available since OpenSSH 2.1.0
(mac) hmac-sha1-96 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.47
(mac) hmac-md5-96 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.5.0
# fingerprints
(fin) ssh-rsa: SHA256:YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4
# algorithm recommendations (for OpenSSH 5.6)
(rec) !diffie-hellman-group-exchange-sha256 -- kex algorithm to change (increase modulus size to 2048 bits or larger) 
(rec) -3des-cbc -- enc algorithm to remove 
(rec) -aes128-cbc -- enc algorithm to remove 
(rec) -aes192-cbc -- enc algorithm to remove 
(rec) -aes256-cbc -- enc algorithm to remove 
(rec) -arcfour -- enc algorithm to remove 
(rec) -arcfour128 -- enc algorithm to remove 
(rec) -arcfour256 -- enc algorithm to remove 
(rec) -blowfish-cbc -- enc algorithm to remove 
(rec) -cast128-cbc -- enc algorithm to remove 
(rec) -diffie-hellman-group-exchange-sha1 -- kex algorithm to remove 
(rec) -diffie-hellman-group1-sha1 -- kex algorithm to remove 
(rec) -hmac-md5 -- mac algorithm to remove 
(rec) -hmac-md5-96 -- mac algorithm to remove 
(rec) -hmac-ripemd160 -- mac algorithm to remove 
(rec) -hmac-ripemd160@openssh.com -- mac algorithm to remove 
(rec) -hmac-sha1-96 -- mac algorithm to remove 
(rec) -rijndael-cbc@lysator.liu.se -- enc algorithm to remove 
(rec) -ssh-rsa -- key algorithm to remove 
(rec) -ssh-rsa-cert-v01@openssh.com -- key algorithm to remove 
(rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove 
(rec) -hmac-sha1 -- mac algorithm to remove 
(rec) -umac-64@openssh.com -- mac algorithm to remove 
# additional info
(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html>

View File

@ -0,0 +1 @@
{"banner": {"comments": null, "protocol": [2, 0], "raw": "SSH-2.0-OpenSSH_5.6", "software": "OpenSSH_5.6"}, "compression": ["none", "zlib@openssh.com"], "enc": ["aes128-ctr", "aes192-ctr", "aes256-ctr", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "blowfish-cbc", "cast128-cbc", "aes192-cbc", "aes256-cbc", "arcfour", "rijndael-cbc@lysator.liu.se"], "fingerprints": [{"fp": "SHA256:YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4", "type": "ssh-rsa"}], "kex": [{"algorithm": "diffie-hellman-group-exchange-sha256", "keysize": 1024}, {"algorithm": "diffie-hellman-group-exchange-sha1", "keysize": 1024}, {"algorithm": "diffie-hellman-group14-sha1"}, {"algorithm": "diffie-hellman-group1-sha1"}], "key": [{"algorithm": "ssh-rsa", "keysize": 1024}, {"algorithm": "ssh-rsa-cert-v01@openssh.com", "casize": 3072, "keysize": 1024}], "mac": ["hmac-md5", "hmac-sha1", "umac-64@openssh.com", "hmac-ripemd160", "hmac-ripemd160@openssh.com", "hmac-sha1-96", "hmac-md5-96"], "target": "localhost"}

View File

@ -0,0 +1,153 @@
# general
(gen) banner: SSH-2.0-OpenSSH_5.6
(gen) software: OpenSSH 5.6
(gen) compatibility: OpenSSH 5.6-6.6, Dropbear SSH 0.53+ (some functionality from 0.52)
(gen) compression: enabled (zlib@openssh.com)
# security
(cve) CVE-2018-15473 -- (CVSSv2: 5.3) enumerate usernames due to timing discrepencies
(cve) CVE-2016-3115 -- (CVSSv2: 5.5) bypass command restrictions via crafted X11 forwarding data
(cve) CVE-2016-1907 -- (CVSSv2: 5.0) cause DoS via crafted network traffic (out of bounds read)
(cve) CVE-2015-6564 -- (CVSSv2: 6.9) privilege escalation via leveraging sshd uid
(cve) CVE-2015-6563 -- (CVSSv2: 1.9) conduct impersonation attack
(cve) CVE-2014-2532 -- (CVSSv2: 5.8) bypass environment restrictions via specific string before wildcard
(cve) CVE-2014-1692 -- (CVSSv2: 7.5) cause DoS via triggering error condition (memory corruption)
(cve) CVE-2012-0814 -- (CVSSv2: 3.5) leak data via debug messages
(cve) CVE-2011-5000 -- (CVSSv2: 3.5) cause DoS via large value in certain length field (memory consumption)
(cve) CVE-2010-5107 -- (CVSSv2: 5.0) cause DoS via large number of connections (slot exhaustion)
(cve) CVE-2010-4755 -- (CVSSv2: 4.0) cause DoS via crafted glob expression (CPU and memory consumption)
(cve) CVE-2010-4478 -- (CVSSv2: 7.5) bypass authentication check via crafted values
# key exchange algorithms
(kex) diffie-hellman-group-exchange-sha256 (1024-bit) -- [fail] using small 1024-bit modulus
`- [info] available since OpenSSH 4.4
(kex) diffie-hellman-group-exchange-sha1 (1024-bit) -- [fail] using small 1024-bit modulus
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.3.0
(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
(kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus
 `- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [fail] disabled (in client) since OpenSSH 7.0, logjam attack
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
# host-key algorithms
(key) ssh-rsa (1024-bit) -- [fail] using weak hashing algorithm
 `- [fail] using small 1024-bit modulus
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28
`- [info] a future deprecation notice has been issued in OpenSSH 8.2: https://www.openssh.com/txt/release-8.2
(key) ssh-rsa-cert-v01@openssh.com (1024-bit cert/3072-bit CA) -- [fail] using weak hashing algorithm
 `- [fail] using small 1024-bit modulus
`- [info] available since OpenSSH 5.6
`- [info] a future deprecation notice has been issued in OpenSSH 8.2: https://www.openssh.com/txt/release-8.2
# encryption algorithms (ciphers)
(enc) aes128-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52
(enc) aes192-ctr -- [info] available since OpenSSH 3.7
(enc) aes256-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52
(enc) arcfour256 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher
`- [info] available since OpenSSH 4.2
(enc) arcfour128 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher
`- [info] available since OpenSSH 4.2
(enc) aes128-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
(enc) 3des-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.4, unsafe algorithm
 `- [warn] using weak cipher
 `- [warn] using weak cipher mode
 `- [warn] using small 64-bit block size
`- [info] available since OpenSSH 1.2.2, Dropbear SSH 0.28
(enc) blowfish-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [fail] disabled since Dropbear SSH 0.53
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher mode
 `- [warn] using small 64-bit block size
`- [info] available since OpenSSH 1.2.2, Dropbear SSH 0.28
(enc) cast128-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher mode
 `- [warn] using small 64-bit block size
`- [info] available since OpenSSH 2.1.0
(enc) aes192-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0
(enc) aes256-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.47
(enc) arcfour -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher
`- [info] available since OpenSSH 2.1.0
(enc) rijndael-cbc@lysator.liu.se -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0
# message authentication code algorithms
(mac) hmac-md5 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28
(mac) hmac-sha1 -- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28
(mac) umac-64@openssh.com -- [warn] using encrypt-and-MAC mode
 `- [warn] using small 64-bit tag size
`- [info] available since OpenSSH 4.7
(mac) hmac-ripemd160 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
`- [info] available since OpenSSH 2.5.0
(mac) hmac-ripemd160@openssh.com -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
`- [info] available since OpenSSH 2.1.0
(mac) hmac-sha1-96 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.47
(mac) hmac-md5-96 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.5.0
# fingerprints
(fin) ssh-rsa: SHA256:YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4
# algorithm recommendations (for OpenSSH 5.6)
(rec) !diffie-hellman-group-exchange-sha256 -- kex algorithm to change (increase modulus size to 2048 bits or larger) 
(rec) -3des-cbc -- enc algorithm to remove 
(rec) -aes128-cbc -- enc algorithm to remove 
(rec) -aes192-cbc -- enc algorithm to remove 
(rec) -aes256-cbc -- enc algorithm to remove 
(rec) -arcfour -- enc algorithm to remove 
(rec) -arcfour128 -- enc algorithm to remove 
(rec) -arcfour256 -- enc algorithm to remove 
(rec) -blowfish-cbc -- enc algorithm to remove 
(rec) -cast128-cbc -- enc algorithm to remove 
(rec) -diffie-hellman-group-exchange-sha1 -- kex algorithm to remove 
(rec) -diffie-hellman-group1-sha1 -- kex algorithm to remove 
(rec) -hmac-md5 -- mac algorithm to remove 
(rec) -hmac-md5-96 -- mac algorithm to remove 
(rec) -hmac-ripemd160 -- mac algorithm to remove 
(rec) -hmac-ripemd160@openssh.com -- mac algorithm to remove 
(rec) -hmac-sha1-96 -- mac algorithm to remove 
(rec) -rijndael-cbc@lysator.liu.se -- enc algorithm to remove 
(rec) -ssh-rsa -- key algorithm to remove 
(rec) -ssh-rsa-cert-v01@openssh.com -- key algorithm to remove 
(rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove 
(rec) -hmac-sha1 -- mac algorithm to remove 
(rec) -umac-64@openssh.com -- mac algorithm to remove 
# additional info
(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html>

View File

@ -0,0 +1 @@
{"banner": {"comments": null, "protocol": [2, 0], "raw": "SSH-2.0-OpenSSH_5.6", "software": "OpenSSH_5.6"}, "compression": ["none", "zlib@openssh.com"], "enc": ["aes128-ctr", "aes192-ctr", "aes256-ctr", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "blowfish-cbc", "cast128-cbc", "aes192-cbc", "aes256-cbc", "arcfour", "rijndael-cbc@lysator.liu.se"], "fingerprints": [{"fp": "SHA256:nsWtdJ9Z67Vrf7OsUzQov7esXhsWAfVppArGh25u244", "type": "ssh-rsa"}], "kex": [{"algorithm": "diffie-hellman-group-exchange-sha256", "keysize": 1024}, {"algorithm": "diffie-hellman-group-exchange-sha1", "keysize": 1024}, {"algorithm": "diffie-hellman-group14-sha1"}, {"algorithm": "diffie-hellman-group1-sha1"}], "key": [{"algorithm": "ssh-rsa", "keysize": 3072}, {"algorithm": "ssh-rsa-cert-v01@openssh.com", "casize": 1024, "keysize": 3072}], "mac": ["hmac-md5", "hmac-sha1", "umac-64@openssh.com", "hmac-ripemd160", "hmac-ripemd160@openssh.com", "hmac-sha1-96", "hmac-md5-96"], "target": "localhost"}

View File

@ -0,0 +1,152 @@
# general
(gen) banner: SSH-2.0-OpenSSH_5.6
(gen) software: OpenSSH 5.6
(gen) compatibility: OpenSSH 5.6-6.6, Dropbear SSH 0.53+ (some functionality from 0.52)
(gen) compression: enabled (zlib@openssh.com)
# security
(cve) CVE-2018-15473 -- (CVSSv2: 5.3) enumerate usernames due to timing discrepencies
(cve) CVE-2016-3115 -- (CVSSv2: 5.5) bypass command restrictions via crafted X11 forwarding data
(cve) CVE-2016-1907 -- (CVSSv2: 5.0) cause DoS via crafted network traffic (out of bounds read)
(cve) CVE-2015-6564 -- (CVSSv2: 6.9) privilege escalation via leveraging sshd uid
(cve) CVE-2015-6563 -- (CVSSv2: 1.9) conduct impersonation attack
(cve) CVE-2014-2532 -- (CVSSv2: 5.8) bypass environment restrictions via specific string before wildcard
(cve) CVE-2014-1692 -- (CVSSv2: 7.5) cause DoS via triggering error condition (memory corruption)
(cve) CVE-2012-0814 -- (CVSSv2: 3.5) leak data via debug messages
(cve) CVE-2011-5000 -- (CVSSv2: 3.5) cause DoS via large value in certain length field (memory consumption)
(cve) CVE-2010-5107 -- (CVSSv2: 5.0) cause DoS via large number of connections (slot exhaustion)
(cve) CVE-2010-4755 -- (CVSSv2: 4.0) cause DoS via crafted glob expression (CPU and memory consumption)
(cve) CVE-2010-4478 -- (CVSSv2: 7.5) bypass authentication check via crafted values
# key exchange algorithms
(kex) diffie-hellman-group-exchange-sha256 (1024-bit) -- [fail] using small 1024-bit modulus
`- [info] available since OpenSSH 4.4
(kex) diffie-hellman-group-exchange-sha1 (1024-bit) -- [fail] using small 1024-bit modulus
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.3.0
(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
(kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus
 `- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [fail] disabled (in client) since OpenSSH 7.0, logjam attack
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
# host-key algorithms
(key) ssh-rsa (3072-bit) -- [fail] using weak hashing algorithm
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28
`- [info] a future deprecation notice has been issued in OpenSSH 8.2: https://www.openssh.com/txt/release-8.2
(key) ssh-rsa-cert-v01@openssh.com (3072-bit cert/1024-bit CA) -- [fail] using weak hashing algorithm
 `- [fail] using small 1024-bit modulus
`- [info] available since OpenSSH 5.6
`- [info] a future deprecation notice has been issued in OpenSSH 8.2: https://www.openssh.com/txt/release-8.2
# encryption algorithms (ciphers)
(enc) aes128-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52
(enc) aes192-ctr -- [info] available since OpenSSH 3.7
(enc) aes256-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52
(enc) arcfour256 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher
`- [info] available since OpenSSH 4.2
(enc) arcfour128 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher
`- [info] available since OpenSSH 4.2
(enc) aes128-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
(enc) 3des-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.4, unsafe algorithm
 `- [warn] using weak cipher
 `- [warn] using weak cipher mode
 `- [warn] using small 64-bit block size
`- [info] available since OpenSSH 1.2.2, Dropbear SSH 0.28
(enc) blowfish-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [fail] disabled since Dropbear SSH 0.53
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher mode
 `- [warn] using small 64-bit block size
`- [info] available since OpenSSH 1.2.2, Dropbear SSH 0.28
(enc) cast128-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher mode
 `- [warn] using small 64-bit block size
`- [info] available since OpenSSH 2.1.0
(enc) aes192-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0
(enc) aes256-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.47
(enc) arcfour -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher
`- [info] available since OpenSSH 2.1.0
(enc) rijndael-cbc@lysator.liu.se -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0
# message authentication code algorithms
(mac) hmac-md5 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28
(mac) hmac-sha1 -- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28
(mac) umac-64@openssh.com -- [warn] using encrypt-and-MAC mode
 `- [warn] using small 64-bit tag size
`- [info] available since OpenSSH 4.7
(mac) hmac-ripemd160 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
`- [info] available since OpenSSH 2.5.0
(mac) hmac-ripemd160@openssh.com -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
`- [info] available since OpenSSH 2.1.0
(mac) hmac-sha1-96 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.47
(mac) hmac-md5-96 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.5.0
# fingerprints
(fin) ssh-rsa: SHA256:nsWtdJ9Z67Vrf7OsUzQov7esXhsWAfVppArGh25u244
# algorithm recommendations (for OpenSSH 5.6)
(rec) !diffie-hellman-group-exchange-sha256 -- kex algorithm to change (increase modulus size to 2048 bits or larger) 
(rec) -3des-cbc -- enc algorithm to remove 
(rec) -aes128-cbc -- enc algorithm to remove 
(rec) -aes192-cbc -- enc algorithm to remove 
(rec) -aes256-cbc -- enc algorithm to remove 
(rec) -arcfour -- enc algorithm to remove 
(rec) -arcfour128 -- enc algorithm to remove 
(rec) -arcfour256 -- enc algorithm to remove 
(rec) -blowfish-cbc -- enc algorithm to remove 
(rec) -cast128-cbc -- enc algorithm to remove 
(rec) -diffie-hellman-group-exchange-sha1 -- kex algorithm to remove 
(rec) -diffie-hellman-group1-sha1 -- kex algorithm to remove 
(rec) -hmac-md5 -- mac algorithm to remove 
(rec) -hmac-md5-96 -- mac algorithm to remove 
(rec) -hmac-ripemd160 -- mac algorithm to remove 
(rec) -hmac-ripemd160@openssh.com -- mac algorithm to remove 
(rec) -hmac-sha1-96 -- mac algorithm to remove 
(rec) -rijndael-cbc@lysator.liu.se -- enc algorithm to remove 
(rec) -ssh-rsa -- key algorithm to remove 
(rec) -ssh-rsa-cert-v01@openssh.com -- key algorithm to remove 
(rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove 
(rec) -hmac-sha1 -- mac algorithm to remove 
(rec) -umac-64@openssh.com -- mac algorithm to remove 
# additional info
(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html>

View File

@ -0,0 +1 @@
{"banner": {"comments": null, "protocol": [2, 0], "raw": "SSH-2.0-OpenSSH_5.6", "software": "OpenSSH_5.6"}, "compression": ["none", "zlib@openssh.com"], "enc": ["aes128-ctr", "aes192-ctr", "aes256-ctr", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "blowfish-cbc", "cast128-cbc", "aes192-cbc", "aes256-cbc", "arcfour", "rijndael-cbc@lysator.liu.se"], "fingerprints": [{"fp": "SHA256:nsWtdJ9Z67Vrf7OsUzQov7esXhsWAfVppArGh25u244", "type": "ssh-rsa"}], "kex": [{"algorithm": "diffie-hellman-group-exchange-sha256", "keysize": 1024}, {"algorithm": "diffie-hellman-group-exchange-sha1", "keysize": 1024}, {"algorithm": "diffie-hellman-group14-sha1"}, {"algorithm": "diffie-hellman-group1-sha1"}], "key": [{"algorithm": "ssh-rsa", "keysize": 3072}, {"algorithm": "ssh-rsa-cert-v01@openssh.com", "casize": 3072, "keysize": 3072}], "mac": ["hmac-md5", "hmac-sha1", "umac-64@openssh.com", "hmac-ripemd160", "hmac-ripemd160@openssh.com", "hmac-sha1-96", "hmac-md5-96"], "target": "localhost"}

View File

@ -0,0 +1,151 @@
# general
(gen) banner: SSH-2.0-OpenSSH_5.6
(gen) software: OpenSSH 5.6
(gen) compatibility: OpenSSH 5.6-6.6, Dropbear SSH 0.53+ (some functionality from 0.52)
(gen) compression: enabled (zlib@openssh.com)
# security
(cve) CVE-2018-15473 -- (CVSSv2: 5.3) enumerate usernames due to timing discrepencies
(cve) CVE-2016-3115 -- (CVSSv2: 5.5) bypass command restrictions via crafted X11 forwarding data
(cve) CVE-2016-1907 -- (CVSSv2: 5.0) cause DoS via crafted network traffic (out of bounds read)
(cve) CVE-2015-6564 -- (CVSSv2: 6.9) privilege escalation via leveraging sshd uid
(cve) CVE-2015-6563 -- (CVSSv2: 1.9) conduct impersonation attack
(cve) CVE-2014-2532 -- (CVSSv2: 5.8) bypass environment restrictions via specific string before wildcard
(cve) CVE-2014-1692 -- (CVSSv2: 7.5) cause DoS via triggering error condition (memory corruption)
(cve) CVE-2012-0814 -- (CVSSv2: 3.5) leak data via debug messages
(cve) CVE-2011-5000 -- (CVSSv2: 3.5) cause DoS via large value in certain length field (memory consumption)
(cve) CVE-2010-5107 -- (CVSSv2: 5.0) cause DoS via large number of connections (slot exhaustion)
(cve) CVE-2010-4755 -- (CVSSv2: 4.0) cause DoS via crafted glob expression (CPU and memory consumption)
(cve) CVE-2010-4478 -- (CVSSv2: 7.5) bypass authentication check via crafted values
# key exchange algorithms
(kex) diffie-hellman-group-exchange-sha256 (1024-bit) -- [fail] using small 1024-bit modulus
`- [info] available since OpenSSH 4.4
(kex) diffie-hellman-group-exchange-sha1 (1024-bit) -- [fail] using small 1024-bit modulus
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.3.0
(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
(kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus
 `- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [fail] disabled (in client) since OpenSSH 7.0, logjam attack
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
# host-key algorithms
(key) ssh-rsa (3072-bit) -- [fail] using weak hashing algorithm
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28
`- [info] a future deprecation notice has been issued in OpenSSH 8.2: https://www.openssh.com/txt/release-8.2
(key) ssh-rsa-cert-v01@openssh.com (3072-bit cert/3072-bit CA) -- [fail] using weak hashing algorithm
`- [info] available since OpenSSH 5.6
`- [info] a future deprecation notice has been issued in OpenSSH 8.2: https://www.openssh.com/txt/release-8.2
# encryption algorithms (ciphers)
(enc) aes128-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52
(enc) aes192-ctr -- [info] available since OpenSSH 3.7
(enc) aes256-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52
(enc) arcfour256 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher
`- [info] available since OpenSSH 4.2
(enc) arcfour128 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher
`- [info] available since OpenSSH 4.2
(enc) aes128-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
(enc) 3des-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.4, unsafe algorithm
 `- [warn] using weak cipher
 `- [warn] using weak cipher mode
 `- [warn] using small 64-bit block size
`- [info] available since OpenSSH 1.2.2, Dropbear SSH 0.28
(enc) blowfish-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [fail] disabled since Dropbear SSH 0.53
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher mode
 `- [warn] using small 64-bit block size
`- [info] available since OpenSSH 1.2.2, Dropbear SSH 0.28
(enc) cast128-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher mode
 `- [warn] using small 64-bit block size
`- [info] available since OpenSSH 2.1.0
(enc) aes192-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0
(enc) aes256-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.47
(enc) arcfour -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher
`- [info] available since OpenSSH 2.1.0
(enc) rijndael-cbc@lysator.liu.se -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using weak cipher mode
`- [info] available since OpenSSH 2.3.0
# message authentication code algorithms
(mac) hmac-md5 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28
(mac) hmac-sha1 -- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28
(mac) umac-64@openssh.com -- [warn] using encrypt-and-MAC mode
 `- [warn] using small 64-bit tag size
`- [info] available since OpenSSH 4.7
(mac) hmac-ripemd160 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
`- [info] available since OpenSSH 2.5.0
(mac) hmac-ripemd160@openssh.com -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
`- [info] available since OpenSSH 2.1.0
(mac) hmac-sha1-96 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.47
(mac) hmac-md5-96 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm
 `- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm
 `- [warn] using encrypt-and-MAC mode
 `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.5.0
# fingerprints
(fin) ssh-rsa: SHA256:nsWtdJ9Z67Vrf7OsUzQov7esXhsWAfVppArGh25u244
# algorithm recommendations (for OpenSSH 5.6)
(rec) !diffie-hellman-group-exchange-sha256 -- kex algorithm to change (increase modulus size to 2048 bits or larger) 
(rec) -3des-cbc -- enc algorithm to remove 
(rec) -aes128-cbc -- enc algorithm to remove 
(rec) -aes192-cbc -- enc algorithm to remove 
(rec) -aes256-cbc -- enc algorithm to remove 
(rec) -arcfour -- enc algorithm to remove 
(rec) -arcfour128 -- enc algorithm to remove 
(rec) -arcfour256 -- enc algorithm to remove 
(rec) -blowfish-cbc -- enc algorithm to remove 
(rec) -cast128-cbc -- enc algorithm to remove 
(rec) -diffie-hellman-group-exchange-sha1 -- kex algorithm to remove 
(rec) -diffie-hellman-group1-sha1 -- kex algorithm to remove 
(rec) -hmac-md5 -- mac algorithm to remove 
(rec) -hmac-md5-96 -- mac algorithm to remove 
(rec) -hmac-ripemd160 -- mac algorithm to remove 
(rec) -hmac-ripemd160@openssh.com -- mac algorithm to remove 
(rec) -hmac-sha1-96 -- mac algorithm to remove 
(rec) -rijndael-cbc@lysator.liu.se -- enc algorithm to remove 
(rec) -ssh-rsa -- key algorithm to remove 
(rec) -ssh-rsa-cert-v01@openssh.com -- key algorithm to remove 
(rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove 
(rec) -hmac-sha1 -- mac algorithm to remove 
(rec) -umac-64@openssh.com -- mac algorithm to remove 
# additional info
(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html>

View File

@ -0,0 +1 @@
{"errors": [], "host": "localhost", "passed": true, "policy": "Hardened OpenSSH Server v8.0 (version 1)"}

View File

@ -0,0 +1,3 @@
Host: localhost:2222
Policy: Hardened OpenSSH Server v8.0 (version 1)
Result: ✔ Passed

View File

@ -0,0 +1 @@
{"errors": [{"actual": ["umac-64-etm@openssh.com", "umac-128-etm@openssh.com", "hmac-sha2-256-etm@openssh.com", "hmac-sha2-512-etm@openssh.com", "hmac-sha1-etm@openssh.com", "umac-64@openssh.com", "umac-128@openssh.com", "hmac-sha2-256", "hmac-sha2-512", "hmac-sha1"], "expected_optional": [""], "expected_required": ["hmac-sha2-256-etm@openssh.com", "hmac-sha2-512-etm@openssh.com", "umac-128-etm@openssh.com"], "mismatched_field": "MACs"}], "host": "localhost", "passed": false, "policy": "Hardened OpenSSH Server v8.0 (version 1)"}

View File

@ -0,0 +1,9 @@
Host: localhost:2222
Policy: Hardened OpenSSH Server v8.0 (version 1)
Result: ❌ Failed!

Errors:
* MACs did not match.
- Expected: hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com
- Actual: umac-64-etm@openssh.com, umac-128-etm@openssh.com, hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha1-etm@openssh.com, umac-64@openssh.com, umac-128@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha1


View File

@ -0,0 +1 @@
{"errors": [], "host": "localhost", "passed": true, "policy": "Docker policy: test11 (version 1)"}

View File

@ -0,0 +1,3 @@
Host: localhost:2222
Policy: Docker policy: test11 (version 1)
Result: ✔ Passed

View File

@ -0,0 +1 @@
{"errors": [{"actual": ["3072"], "expected_optional": [""], "expected_required": ["4096"], "mismatched_field": "RSA host key (rsa-sha2-256) sizes"}, {"actual": ["3072"], "expected_optional": [""], "expected_required": ["4096"], "mismatched_field": "RSA host key (rsa-sha2-512) sizes"}, {"actual": ["3072"], "expected_optional": [""], "expected_required": ["4096"], "mismatched_field": "RSA host key (ssh-rsa) sizes"}], "host": "localhost", "passed": false, "policy": "Docker policy: test12 (version 1)"}

View File

@ -0,0 +1,17 @@
Host: localhost:2222
Policy: Docker policy: test12 (version 1)
Result: ❌ Failed!

Errors:
* RSA host key (rsa-sha2-256) sizes did not match.
- Expected: 4096
- Actual: 3072
* RSA host key (rsa-sha2-512) sizes did not match.
- Expected: 4096
- Actual: 3072
* RSA host key (ssh-rsa) sizes did not match.
- Expected: 4096
- Actual: 3072


Some files were not shown because too many files have changed in this diff Show More