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

82 Commits

Author SHA1 Message Date
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
106 changed files with 7582 additions and 5928 deletions

View File

@ -1,4 +1,4 @@
version: '1.7.1.dev.{build}' version: 'v2.2.1-dev.{build}'
build: off build: off
branches: branches:
@ -8,18 +8,14 @@ branches:
environment: environment:
matrix: matrix:
- PYTHON: "C:\\Python26"
- PYTHON: "C:\\Python26-x64"
- PYTHON: "C:\\Python27"
- PYTHON: "C:\\Python27-x64"
- PYTHON: "C:\\Python33"
- PYTHON: "C:\\Python33-x64"
- PYTHON: "C:\\Python34"
- PYTHON: "C:\\Python34-x64"
- PYTHON: "C:\\Python35" - PYTHON: "C:\\Python35"
- PYTHON: "C:\\Python35-x64" - PYTHON: "C:\\Python35-x64"
- PYTHON: "C:\\Python36" - PYTHON: "C:\\Python36"
- PYTHON: "C:\\Python36-x64" - PYTHON: "C:\\Python36-x64"
- PYTHON: "C:\\Python37"
- PYTHON: "C:\\Python37-x64"
- PYTHON: "C:\\Python38"
- PYTHON: "C:\\Python38-x64"
matrix: matrix:
fast_finish: true fast_finish: true

8
.deepsource.toml Normal file
View File

@ -0,0 +1,8 @@
version = 1
[[analyzers]]
name = "python"
enabled = true
[analyzers.meta]
runtime_version = "3.x.x"

14
.gitignore vendored
View File

@ -1,11 +1,19 @@
*~ *~
*.pyc *.pyc
*.exe
*.asc
venv*/ venv*/
.cache/ .cache/
.mypy_cache/
.tox .tox
.coverage* .coverage*
reports/ reports/
.scannerwork/ .scannerwork/
pypi/sshaudit/LICENSE packages/sshaudit/LICENSE
pypi/sshaudit/README.md packages/sshaudit/README.md
pypi/sshaudit/sshaudit.py packages/sshaudit/sshaudit.py
packages/parts/
packages/prime/
packages/snap/
packages/stage/
packages/ssh-audit_*.snap

View File

@ -1,80 +1,19 @@
language: python language: python
sudo: false
matrix: python:
include: - "3.5"
# (default) - "3.6"
- os: linux - "3.7"
python: 2.6 - "3.8"
- os: linux
python: 2.7
env: SQ=1
- os: linux
python: 3.3
- os: linux
python: 3.4
- os: linux
python: 3.5
- os: linux
python: 3.6
- os: linux
python: pypy
- os: linux
python: pypy3
- os: linux
python: 3.7-dev
# Ubuntu 12.04
- os: linux
dist: precise
language: generic
env: PY_VER=py26,py27,py33,py34,py35,py36,pypy,pypy3 PY_ORIGIN=pyenv
# Ubuntu 14.04
- os: linux
dist: trusty
language: generic
env: PY_VER=py26,py27,py33,py34,py35,py36,pypy,pypy3 PY_ORIGIN=pyenv
# macOS 10.12 Sierra
- os: osx
osx_image: xcode8.3
language: generic
env: PY_VER=py26,py27,py33,py34,py35,py36,pypy,pypy3
# Mac OS X 10.11 El Capitan
- os: osx
osx_image: xcode7.3
language: generic
env: PY_VER=py26,py27,py33,py34,py35,py36,pypy,pypy3
# Mac OS X 10.10 Yosemite
- os: osx
osx_image: xcode6.4
language: generic
env: PY_VER=py26,py27,py33,py34,py35,py36,pypy,pypy3
allow_failures:
# PyPy3 on Travis CI is out of date
- python: pypy3
# Python nightly could fail
- python: 3.7-dev
- env: PY_VER=py37
- env: PY_VER=py37/pyenv
- env: PY_VER=py37 PY_ORIGIN=pyenv
fast_finish: true
cache: cache:
- pip - pip
- directories:
- $HOME/.pyenv.cache
- $HOME/.bin
before_install:
- source test/tools/ci-linux.sh
- ci_step_before_install
install: install:
- ci_step_install - pip install -U pip tox tox-travis coveralls codecov
script: script:
- ci_step_script - tox
after_success: after_success:
- ci_step_success - codecov
after_failure:
- ci_step_failure

26
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,26 @@
# 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 (please submit pull requests to the `dev` branch).
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/).
Install tox with `apt install tox`, then simply run `tox` in the top-level directory. Look for any error messages in the (verbose) output.
## 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.

View File

@ -1,7 +1,7 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (C) 2017-2020 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu) Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Copyright (C) 2017-2019 Joe Testa (jtesta@positronsecurity.com)
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy

130
README.md
View File

@ -7,6 +7,8 @@
--> -->
**ssh-audit** is a tool for ssh server & client configuration auditing. **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 ## Features
- SSH1 and SSH2 protocol server support; - SSH1 and SSH2 protocol server support;
- analyze SSH client configuration; - analyze SSH client configuration;
@ -17,44 +19,158 @@
- output security information (related issues, assigned CVE list, etc); - output security information (related issues, assigned CVE list, etc);
- analyze SSH version compatibility based on algorithm information; - analyze SSH version compatibility based on algorithm information;
- historical information from OpenSSH, Dropbear SSH and libssh; - historical information from OpenSSH, Dropbear SSH and libssh;
- policy scans to ensure adherence to a hardened/standard configuration;
- runs on Linux and Windows;
- no dependencies - no dependencies
## Usage ## Usage
``` ```
usage: ssh-audit.py [-1246pbcnjvlt] <host> usage: ssh-audit.py [options] <host>
-h, --help print this help
-1, --ssh1 force ssh version 1 only -1, --ssh1 force ssh version 1 only
-2, --ssh2 force ssh version 2 only -2, --ssh2 force ssh version 2 only
-4, --ipv4 enable IPv4 (order of precedence) -4, --ipv4 enable IPv4 (order of precedence)
-6, --ipv6 enable IPv6 (order of precedence) -6, --ipv6 enable IPv6 (order of precedence)
-p, --port=<port> port to connect
-b, --batch batch output -b, --batch batch output
-c, --client-audit starts a server on port 2222 to audit client -c, --client-audit starts a server on port 2222 to audit client
software config (use -p to change port; software config (use -p to change port;
use -t to change timeout) use -t to change timeout)
-n, --no-colors disable colors
-j, --json JSON output -j, --json JSON output
-v, --verbose verbose output
-l, --level=<level> minimum output level (info|warn|fail) -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, --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.txt> run a policy test using the specified policy
-t, --timeout=<secs> timeout (in seconds) for connection and reading -t, --timeout=<secs> timeout (in seconds) for connection and reading
(default: 5) (default: 5)
-T, --targets=<hosts.txt> a file containing a list of target hosts (one
per line, format HOST[:PORT])
-v, --verbose verbose output
``` ```
* if both IPv4 and IPv6 are used, order of precedence can be set by using either `-46` or `-64`. * 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). * 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. * 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).
### Server Audit Example Basic server auditing:
Below is a screen shot of the server-auditing output when connecting to an unhardened OpenSSH v5.3 service: ```
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 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 file paths with `-P`/`--policy`):
```
ssh-audit -L
```
To run a policy audit against a server:
```
ssh-audit -P path/to/server_policy targetserver
```
To run a policy audit against a client:
```
ssh-audit -c -P path/to/client_policy
```
To run a policy audit against many servers:
```
ssh-audit -T servers.txt -P path/to/server_policy
```
To create a policy based on a target server (which can be manually edited; see official built-in policies for syntax examples):
```
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) ![screenshot](https://user-images.githubusercontent.com/2982011/64388792-317e6f80-d00e-11e9-826e-a4934769bb07.png)
### Client Audit Example ### 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:
![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: 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) ![client_screenshot](https://user-images.githubusercontent.com/2982011/68867998-b946c100-06c4-11ea-975f-1f47e4178a74.png)
### Hardening Guides ### 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) 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
```
### 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 ## ChangeLog
### 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) ### 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 host key types: `rsa-sha2-256-cert-v01@openssh.com`, `rsa-sha2-512-cert-v01@openssh.com`.
- Added 2 new ciphers: `des`, `3des`. - Added 2 new ciphers: `des`, `3des`.

View File

@ -28,6 +28,12 @@ GREEN="\033[0;32m"
REDB="\033[1;31m" # Red + bold REDB="\033[1;31m" # Red + bold
GREENB="\033[1;32m" # Green + 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
# Returns 0 if current docker image exists. # Returns 0 if current docker image exists.
function check_if_docker_image_exists { function check_if_docker_image_exists {
@ -353,8 +359,9 @@ function run_dropbear_test {
dropbear_version=$1 dropbear_version=$1
test_number=$2 test_number=$2
options=$3 options=$3
expected_retval=$4
run_test 'Dropbear' $dropbear_version $test_number "$options" run_test 'Dropbear' $dropbear_version $test_number "$options" $expected_retval
} }
@ -363,8 +370,9 @@ function run_dropbear_test {
function run_openssh_test { function run_openssh_test {
openssh_version=$1 openssh_version=$1
test_number=$2 test_number=$2
expected_retval=$3
run_test 'OpenSSH' $openssh_version $test_number '' run_test 'OpenSSH' $openssh_version $test_number '' $expected_retval
} }
@ -373,8 +381,9 @@ function run_openssh_test {
function run_tinyssh_test { function run_tinyssh_test {
tinyssh_version=$1 tinyssh_version=$1
test_number=$2 test_number=$2
expected_retval=$3
run_test 'TinySSH' $tinyssh_version $test_number '' run_test 'TinySSH' $tinyssh_version $test_number '' $expected_retval
} }
@ -383,6 +392,7 @@ function run_test {
version=$2 version=$2
test_number=$3 test_number=$3
options=$4 options=$4
expected_retval=$5
server_exec= server_exec=
test_result_stdout= test_result_stdout=
@ -421,20 +431,22 @@ function run_test {
fi fi
./ssh-audit.py localhost:2222 > $test_result_stdout ./ssh-audit.py localhost:2222 > $test_result_stdout
if [[ $? != 0 ]]; then actual_retval=$?
echo -e "${REDB}Failed to run ssh-audit.py! (exit code: $?)${CLR}" if [[ $actual_retval != $expected_retval ]]; then
docker container stop $cid > /dev/null echo -e "${REDB}Unexpected return value. Expected: ${expected_retval}; Actual: ${actual_retval}${CLR}"
docker container stop -t 0 $cid > /dev/null
exit 1 exit 1
fi fi
./ssh-audit.py -j localhost:2222 > $test_result_json ./ssh-audit.py -j localhost:2222 > $test_result_json
if [[ $? != 0 ]]; then actual_retval=$?
echo -e "${REDB}Failed to run ssh-audit.py! (exit code: $?)${CLR}" if [[ $actual_retval != $expected_retval ]]; then
docker container stop $cid > /dev/null echo -e "${REDB}Unexpected return value. Expected: ${expected_retval}; Actual: ${actual_retval}${CLR}"
docker container stop -t 0 $cid > /dev/null
exit 1 exit 1
fi fi
docker container stop $cid > /dev/null docker container stop -t 0 $cid > /dev/null
if [[ $? != 0 ]]; then if [[ $? != 0 ]]; then
echo -e "${REDB}Failed to stop docker container ${cid}! (exit code: $?)${CLR}" echo -e "${REDB}Failed to stop docker container ${cid}! (exit code: $?)${CLR}"
exit 1 exit 1
@ -465,6 +477,81 @@ function run_test {
} }
function run_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}_policy_${test_number}.txt"
test_result_json="${TEST_RESULT_DIR}/openssh_${version}_policy_${test_number}.json"
expected_result_stdout="test/docker/expected_results/openssh_${version}_policy_${test_number}.txt"
expected_result_json="test/docker/expected_results/openssh_${version}_policy_${test_number}.json"
test_name="OpenSSH ${version} policy ${test_number}"
#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. # First check if docker is functional.
docker version > /dev/null docker version > /dev/null
if [[ $? != 0 ]]; then if [[ $? != 0 ]]; then
@ -487,21 +574,54 @@ TEST_RESULT_DIR=`mktemp -d /tmp/ssh-audit_test-results_XXXXXXXXXX`
# Now run all the tests. # Now run all the tests.
echo -e "\nRunning tests..." echo -e "\nRunning tests..."
run_openssh_test '4.0p1' 'test1' run_openssh_test '4.0p1' 'test1' $PROGRAM_RETVAL_FAILURE
echo echo
run_openssh_test '5.6p1' 'test1' run_openssh_test '5.6p1' 'test1' $PROGRAM_RETVAL_FAILURE
run_openssh_test '5.6p1' 'test2' run_openssh_test '5.6p1' 'test2' $PROGRAM_RETVAL_FAILURE
run_openssh_test '5.6p1' 'test3' run_openssh_test '5.6p1' 'test3' $PROGRAM_RETVAL_FAILURE
run_openssh_test '5.6p1' 'test4' run_openssh_test '5.6p1' 'test4' $PROGRAM_RETVAL_FAILURE
run_openssh_test '5.6p1' 'test5' run_openssh_test '5.6p1' 'test5' $PROGRAM_RETVAL_FAILURE
echo echo
run_openssh_test '8.0p1' 'test1' run_openssh_test '8.0p1' 'test1' $PROGRAM_RETVAL_FAILURE
run_openssh_test '8.0p1' 'test2' run_openssh_test '8.0p1' 'test2' $PROGRAM_RETVAL_FAILURE
run_openssh_test '8.0p1' 'test3' run_openssh_test '8.0p1' 'test3' $PROGRAM_RETVAL_GOOD
echo 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' 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 echo
run_tinyssh_test '20190101' 'test1' run_tinyssh_test '20190101' 'test1' $PROGRAM_RETVAL_WARNING
echo
echo
run_policy_test 'config1' 'test1' $PROGRAM_RETVAL_GOOD
run_policy_test 'config1' 'test2' $PROGRAM_RETVAL_FAILURE
run_policy_test 'config1' 'test3' $PROGRAM_RETVAL_FAILURE
run_policy_test 'config1' 'test4' $PROGRAM_RETVAL_FAILURE
run_policy_test 'config1' 'test5' $PROGRAM_RETVAL_FAILURE
run_policy_test 'config2' 'test6' $PROGRAM_RETVAL_GOOD
# Passing test with host key certificate and CA key certificates.
run_policy_test 'config3' 'test7' $PROGRAM_RETVAL_GOOD
# Failing test with host key certificate and non-compliant CA key length.
run_policy_test 'config3' 'test8' $PROGRAM_RETVAL_FAILURE
# Failing test with non-compliant host key certificate and CA key certificate.
run_policy_test 'config3' 'test9' $PROGRAM_RETVAL_FAILURE
# Failing test with non-compliant host key certificate and non-compliant CA key certificate.
run_policy_test 'config3' 'test10' $PROGRAM_RETVAL_FAILURE
# Passing test with host key size check.
run_policy_test 'config2' 'test11' $PROGRAM_RETVAL_GOOD
# Failing test with non-compliant host key size check.
run_policy_test 'config2' 'test12' $PROGRAM_RETVAL_FAILURE
# Passing test with DH modulus test.
run_policy_test 'config2' 'test13' $PROGRAM_RETVAL_GOOD
# Failing test with DH modulus test.
run_policy_test 'config2' 'test14' $PROGRAM_RETVAL_FAILURE
# The test functions above will terminate the script on failure, so if we reached here, # The test functions above will terminate the script on failure, so if we reached here,
# all tests are successful. # all tests are successful.

View File

@ -11,4 +11,4 @@ uploadprod:
twine upload dist/* twine upload dist/*
clean: clean:
rm -rf build/ dist/ *.egg-info/ sshaudit/sshaudit.py sshaudit/LICENSE sshaudit/README.md rm -rf parts/ prime/ snap/ stage/ build/ dist/ *.egg-info/ sshaudit/sshaudit.py sshaudit/LICENSE sshaudit/README.md ssh-audit*.snap

8
packages/Makefile.snap Normal file
View File

@ -0,0 +1,8 @@
all:
cp ../ssh-audit.py sshaudit/sshaudit.py
cp ../README.md sshaudit/README.md
echo -e "\n\nDid you remember to bump the version number in snapcraft.yaml?\n\n"
snapcraft
clean:
rm -rf parts/ prime/ snap/ stage/ build/ dist/ *.egg-info/ sshaudit/sshaudit.py sshaudit/LICENSE sshaudit/README.md ssh-audit*.snap

41
packages/notes.txt Normal file
View File

@ -0,0 +1,41 @@
= PyPI =
To create package and upload to test server:
# apt install virtualenv
$ virtualenv -p /usr/bin/python3 /tmp/pypi_upload
$ cd /tmp/pypi_upload; source bin/activate
$ pip3 install twine
$ cp -R path/to/ssh-audit .
$ cd ssh-audit/packages
$ 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:
$ cd /tmp/pypi_upload; source bin/activate
$ cd ssh-audit/pypi
$ 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, simply run:
$ make -f Makefile.snap

58
packages/setup.py Normal file
View File

@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
import re
import sys
from setuptools import setup
print_warning = False
m = re.search(r'^VERSION\s*=\s*\'v(\d\.\d\.\d)\'', open('sshaudit/sshaudit.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('sshaudit/sshaudit.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)
with open("sshaudit/README.md", "rb") as f:
long_descr = f.read().decode("utf-8")
setup(
name="ssh-audit",
packages=["sshaudit"],
license='MIT',
entry_points={
"console_scripts": ['ssh-audit = sshaudit.sshaudit:main']
},
version=version,
description="An SSH server & client configuration security auditing tool",
long_description=long_descr,
long_description_content_type="text/markdown",
author="Joe Testa",
author_email="jtesta@positronsecurity.com",
url="https://github.com/jtesta/ssh-audit",
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.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Security",
"Topic :: Security :: Cryptography"
])
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
packages/snapcraft.yaml Normal file
View File

@ -0,0 +1,21 @@
name: ssh-audit
version: '2.2.0-1'
license: 'MIT'
summary: ssh-audit
description: |
SSH server and client security configuration auditor. Official repository: <https://github.com/jtesta/ssh-audit>
base: core18
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

@ -0,0 +1,17 @@
Below are notes for creating a Windows executable.
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.7.x from https://www.python.org/. (As of this writing v3.8.0 isn't supported.) 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:
pyinstaller -F --icon packages\windows_icon.ico ssh-audit.py
4.) The 'dist' folder will have the resulting ssh-audit.exe.

BIN
packages/windows_icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

24
policies/openssh_7_7.txt Normal file
View File

@ -0,0 +1,24 @@
#
# Official policy for hardened OpenSSH v7.7.
#
name = "Hardened OpenSSH v7.7"
version = 1
# Group exchange DH modulus sizes.
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
# The host key types that must match exactly (order matters).
host keys = ssh-ed25519
# Host key types that may optionally appear.
optional host keys = 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 = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256
# The ciphers that must match exactly (order matters).
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
# The MACs that must match exactly (order matters).
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com

24
policies/openssh_7_8.txt Normal file
View File

@ -0,0 +1,24 @@
#
# Official policy for hardened OpenSSH v7.8.
#
name = "Hardened OpenSSH v7.8"
version = 1
# Group exchange DH modulus sizes.
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
# The host key types that must match exactly (order matters).
host keys = ssh-ed25519
# Host key types that may optionally appear.
optional host keys = 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 = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256
# The ciphers that must match exactly (order matters).
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
# The MACs that must match exactly (order matters).
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com

24
policies/openssh_7_9.txt Normal file
View File

@ -0,0 +1,24 @@
#
# Official policy for hardened OpenSSH v7.9.
#
name = "Hardened OpenSSH v7.9"
version = 1
# Group exchange DH modulus sizes.
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
# The host key types that must match exactly (order matters).
host keys = ssh-ed25519
# Host key types that may optionally appear.
optional host keys = 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 = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256
# The ciphers that must match exactly (order matters).
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
# The MACs that must match exactly (order matters).
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com

24
policies/openssh_8_0.txt Normal file
View File

@ -0,0 +1,24 @@
#
# Official policy for hardened OpenSSH v8.0.
#
name = "Hardened OpenSSH v8.0"
version = 1
# Group exchange DH modulus sizes.
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
# The host key types that must match exactly (order matters).
host keys = ssh-ed25519
# Host key types that may optionally appear.
optional host keys = 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 = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256
# The ciphers that must match exactly (order matters).
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
# The MACs that must match exactly (order matters).
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com

24
policies/openssh_8_1.txt Normal file
View File

@ -0,0 +1,24 @@
#
# Official policy for hardened OpenSSH v8.1.
#
name = "Hardened OpenSSH v8.1"
version = 1
# Group exchange DH modulus sizes.
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
# The host key types that must match exactly (order matters).
host keys = ssh-ed25519
# Host key types that may optionally appear.
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
# The key exchange algorithms that must match exactly (order matters).
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256
# The ciphers that must match exactly (order matters).
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
# The MACs that must match exactly (order matters).
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com

28
policies/openssh_8_2.txt Normal file
View File

@ -0,0 +1,28 @@
#
# Official policy for hardened OpenSSH v8.2.
#
name = "Hardened OpenSSH v8.2"
version = 1
# RSA host key sizes.
hostkey_size_rsa-sha2-256 = 4096
hostkey_size_rsa-sha2-512 = 4096
# Group exchange DH modulus sizes.
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
# The host key types that must match exactly (order matters).
host keys = rsa-sha2-512, rsa-sha2-256, ssh-ed25519
# Host key types that may optionally appear.
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
# The key exchange algorithms that must match exactly (order matters).
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256
# The ciphers that must match exactly (order matters).
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
# The MACs that must match exactly (order matters).
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com

28
policies/openssh_8_3.txt Normal file
View File

@ -0,0 +1,28 @@
#
# Official policy for hardened OpenSSH v8.3.
#
name = "Hardened OpenSSH v8.3"
version = 1
# RSA host key sizes.
hostkey_size_rsa-sha2-256 = 4096
hostkey_size_rsa-sha2-512 = 4096
# Group exchange DH modulus sizes.
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
# The host key types that must match exactly (order matters).
host keys = rsa-sha2-512, rsa-sha2-256, ssh-ed25519
# Host key types that may optionally appear.
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
# The key exchange algorithms that must match exactly (order matters).
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256
# The ciphers that must match exactly (order matters).
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
# The MACs that must match exactly (order matters).
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com

28
policies/openssh_8_4.txt Normal file
View File

@ -0,0 +1,28 @@
#
# Official policy for hardened OpenSSH v8.4.
#
name = "Hardened OpenSSH v8.4"
version = 1
# RSA host key sizes.
hostkey_size_rsa-sha2-256 = 4096
hostkey_size_rsa-sha2-512 = 4096
# Group exchange DH modulus sizes.
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
# The host key types that must match exactly (order matters).
host keys = rsa-sha2-512, rsa-sha2-256, ssh-ed25519
# Host key types that may optionally appear.
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
# The key exchange algorithms that must match exactly (order matters).
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256
# The ciphers that must match exactly (order matters).
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
# The MACs that must match exactly (order matters).
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com

View File

@ -0,0 +1,19 @@
#
# Official policy for hardened OpenSSH on Ubuntu 16.04 LTS.
#
client policy = true
name = "Hardened Ubuntu Client 16.04 LTS"
version = 1
# The host key types that must match exactly (order matters).
host keys = ssh-ed25519, ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256, rsa-sha2-512, ssh-rsa-cert-v01@openssh.com
# The key exchange algorithms that must match exactly (order matters).
key exchanges = curve25519-sha256@libssh.org, diffie-hellman-group-exchange-sha256, ext-info-c
# The ciphers that must match exactly (order matters).
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
# The MACs that must match exactly (order matters).
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com

View File

@ -0,0 +1,19 @@
#
# Official policy for hardened OpenSSH on Ubuntu 18.04 LTS.
#
client policy = true
name = "Hardened Ubuntu Client 18.04 LTS"
version = 1
# The host key types that must match exactly (order matters).
host keys = ssh-ed25519, ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256, rsa-sha2-512, ssh-rsa-cert-v01@openssh.com
# The key exchange algorithms that must match exactly (order matters).
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256, ext-info-c
# The ciphers that must match exactly (order matters).
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
# The MACs that must match exactly (order matters).
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com

View File

@ -0,0 +1,19 @@
#
# Official policy for hardened OpenSSH on Ubuntu 20.04 LTS.
#
client policy = true
name = "Hardened Ubuntu Client 20.04 LTS"
version = 1
# The host key types that must match exactly (order matters).
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, ssh-rsa-cert-v01@openssh.com
# The key exchange algorithms that must match exactly (order matters).
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256, ext-info-c
# The ciphers that must match exactly (order matters).
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
# The MACs that must match exactly (order matters).
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com

View File

@ -0,0 +1,24 @@
#
# Official policy for hardened OpenSSH on Ubuntu Server 16.04 LTS.
#
name = "Hardened Ubuntu Server 16.04 LTS"
version = 1
# Group exchange DH modulus sizes.
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
# The host key types that must match exactly (order matters).
host keys = ssh-ed25519
# Host key types that may optionally appear.
optional host keys = 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 = curve25519-sha256@libssh.org, diffie-hellman-group-exchange-sha256
# The ciphers that must match exactly (order matters).
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
# The MACs that must match exactly (order matters).
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com

View File

@ -0,0 +1,24 @@
#
# Official policy for hardened OpenSSH on Ubuntu Server 18.04 LTS.
#
name = "Hardened Ubuntu Server 18.04 LTS"
version = 1
# Group exchange DH modulus sizes.
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
# The host key types that must match exactly (order matters).
host keys = ssh-ed25519
# Host key types that may optionally appear.
optional host keys = 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 = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256
# The ciphers that must match exactly (order matters).
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
# The MACs that must match exactly (order matters).
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com

View File

@ -0,0 +1,28 @@
#
# Official policy for hardened OpenSSH on Ubuntu Server 20.04 LTS.
#
name = "Hardened Ubuntu Server 20.04 LTS"
version = 1
# RSA host key sizes.
hostkey_size_rsa-sha2-256 = 4096
hostkey_size_rsa-sha2-512 = 4096
# Group exchange DH modulus sizes.
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
# The host key types that must match exactly (order matters).
host keys = rsa-sha2-512, rsa-sha2-256, ssh-ed25519
# Host key types that may optionally appear.
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
# The key exchange algorithms that must match exactly (order matters).
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256
# The ciphers that must match exactly (order matters).
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
# The MACs that must match exactly (order matters).
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com

View File

@ -1,17 +0,0 @@
To create package and upload to test server:
# apt install virtualenv
$ virtualenv -p /usr/bin/python3 pypi_upload
$ cd pypi_upload; source bin/activate
$ pip3 install twine
$ cp -R path/to/ssh-audit .
$ cd ssh-audit/pypi
$ make
$ make uploadtest
To download from test server and verify:
$ virtualenv -p /usr/bin/python3 pypi_test
$ cd pypi_test; source bin/activate
$ pip3 install --index-url https://test.pypi.org/simple ssh-audit

View File

@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
import re
from setuptools import setup
version = re.search('^VERSION\s*=\s*\'v(\d\.\d\.\d)\'', open('sshaudit/sshaudit.py').read(), re.M).group(1)
print("\n\nPackaging ssh-audit v%s...\n\n" % version)
with open("sshaudit/README.md", "rb") as f:
long_descr = f.read().decode("utf-8")
setup(
name = "ssh-audit",
packages = ["sshaudit"],
license = 'MIT',
entry_points = {
"console_scripts": ['ssh-audit = sshaudit.sshaudit:main']
},
version = version,
description = "An SSH server configuration security auditing tool",
long_description = long_descr,
long_description_content_type = "text/markdown",
author = "Joe Testa",
author_email = "jtesta@positronsecurity.com",
url = "https://github.com/jtesta/ssh-audit",
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",
"Topic :: Security",
"Topic :: Security :: Cryptography"
])

221
ssh-audit.1 Normal file
View File

@ -0,0 +1,221 @@
.TH SSH-AUDIT 1 "July 16, 2020"
.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 file paths can then be provided using -P/--policy=<path/to/policy.txt>.
.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, \-\-make-policy=<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=<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].
.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=<policy.txt> 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 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 resulting file paths with -P/--policy):
.RS
.nf
ssh-audit -L
.fi
.RE
.LP
To run a policy audit against a server:
.RS
.nf
ssh-audit -P path/to/server_policy targetserver
.fi
.RE
.LP
To run a policy audit against a client:
.RS
.nf
ssh-audit -c -P path/to/client_policy
.fi
.RE
.LP
To run a policy audit against many servers:
.RS
.nf
ssh-audit -T servers.txt -P path/to/server_policy
.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,5 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os import os
import io import io
import sys import sys
@ -7,150 +5,143 @@ import socket
import pytest import pytest
if sys.version_info[0] == 2:
import StringIO # pylint: disable=import-error
StringIO = StringIO.StringIO
else:
StringIO = io.StringIO
@pytest.fixture(scope='module') @pytest.fixture(scope='module')
def ssh_audit(): def ssh_audit():
__rdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..') __rdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
sys.path.append(os.path.abspath(__rdir)) sys.path.append(os.path.abspath(__rdir))
return __import__('ssh-audit') return __import__('ssh-audit')
# pylint: disable=attribute-defined-outside-init # pylint: disable=attribute-defined-outside-init
class _OutputSpy(list): class _OutputSpy(list):
def begin(self): def begin(self):
self.__out = StringIO() self.__out = io.StringIO()
self.__old_stdout = sys.stdout self.__old_stdout = sys.stdout
sys.stdout = self.__out sys.stdout = self.__out
def flush(self): def flush(self):
lines = self.__out.getvalue().splitlines() lines = self.__out.getvalue().splitlines()
sys.stdout = self.__old_stdout sys.stdout = self.__old_stdout
self.__out = None self.__out = None
return lines return lines
@pytest.fixture(scope='module') @pytest.fixture(scope='module')
def output_spy(): def output_spy():
return _OutputSpy() return _OutputSpy()
class _VirtualGlobalSocket(object): class _VirtualGlobalSocket:
def __init__(self, vsocket): def __init__(self, vsocket):
self.vsocket = vsocket self.vsocket = vsocket
self.addrinfodata = {} self.addrinfodata = {}
# pylint: disable=unused-argument # pylint: disable=unused-argument
def create_connection(self, address, timeout=0, source_address=None): def create_connection(self, address, timeout=0, source_address=None):
# pylint: disable=protected-access # pylint: disable=protected-access
return self.vsocket._connect(address, True) return self.vsocket._connect(address, True)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def socket(self, def socket(self,
family=socket.AF_INET, family=socket.AF_INET,
socktype=socket.SOCK_STREAM, socktype=socket.SOCK_STREAM,
proto=0, proto=0,
fileno=None): fileno=None):
return self.vsocket return self.vsocket
def getaddrinfo(self, host, port, family=0, socktype=0, proto=0, flags=0): def getaddrinfo(self, host, port, family=0, socktype=0, proto=0, flags=0):
key = '{0}#{1}'.format(host, port) key = '{}#{}'.format(host, port)
if key in self.addrinfodata: if key in self.addrinfodata:
data = self.addrinfodata[key] data = self.addrinfodata[key]
if isinstance(data, Exception): if isinstance(data, Exception):
raise data raise data
return data return data
if host == 'localhost': if host == 'localhost':
r = [] r = []
if family in (0, socket.AF_INET): if family in (0, socket.AF_INET):
r.append((socket.AF_INET, 1, 6, '', ('127.0.0.1', port))) r.append((socket.AF_INET, 1, 6, '', ('127.0.0.1', port)))
if family in (0, socket.AF_INET6): if family in (0, socket.AF_INET6):
r.append((socket.AF_INET6, 1, 6, '', ('::1', port))) r.append((socket.AF_INET6, 1, 6, '', ('::1', port)))
return r return r
return [] return []
class _VirtualSocket(object): class _VirtualSocket:
def __init__(self): def __init__(self):
self.sock_address = ('127.0.0.1', 0) self.sock_address = ('127.0.0.1', 0)
self.peer_address = None self.peer_address = None
self._connected = False self._connected = False
self.timeout = -1.0 self.timeout = -1.0
self.rdata = [] self.rdata = []
self.sdata = [] self.sdata = []
self.errors = {} self.errors = {}
self.gsock = _VirtualGlobalSocket(self) self.gsock = _VirtualGlobalSocket(self)
def _check_err(self, method): def _check_err(self, method):
method_error = self.errors.get(method) method_error = self.errors.get(method)
if method_error: if method_error:
raise method_error raise method_error
def connect(self, address): def connect(self, address):
return self._connect(address, False) return self._connect(address, False)
def _connect(self, address, ret=True): def _connect(self, address, ret=True):
self.peer_address = address self.peer_address = address
self._connected = True self._connected = True
self._check_err('connect') self._check_err('connect')
return self if ret else None return self if ret else None
def settimeout(self, timeout): def settimeout(self, timeout):
self.timeout = timeout self.timeout = timeout
def gettimeout(self): def gettimeout(self):
return self.timeout return self.timeout
def getpeername(self): def getpeername(self):
if self.peer_address is None or not self._connected: if self.peer_address is None or not self._connected:
raise socket.error(57, 'Socket is not connected') raise OSError(57, 'Socket is not connected')
return self.peer_address return self.peer_address
def getsockname(self): def getsockname(self):
return self.sock_address return self.sock_address
def bind(self, address): def bind(self, address):
self.sock_address = address self.sock_address = address
def listen(self, backlog): def listen(self, backlog):
pass pass
def accept(self): def accept(self):
# pylint: disable=protected-access # pylint: disable=protected-access
conn = _VirtualSocket() conn = _VirtualSocket()
conn.sock_address = self.sock_address conn.sock_address = self.sock_address
conn.peer_address = ('127.0.0.1', 0) conn.peer_address = ('127.0.0.1', 0)
conn._connected = True conn._connected = True
return conn, conn.peer_address return conn, conn.peer_address
def recv(self, bufsize, flags=0): def recv(self, bufsize, flags=0):
# pylint: disable=unused-argument # pylint: disable=unused-argument
if not self._connected: if not self._connected:
raise socket.error(54, 'Connection reset by peer') raise OSError(54, 'Connection reset by peer')
if not len(self.rdata) > 0: if not len(self.rdata) > 0:
return b'' return b''
data = self.rdata.pop(0) data = self.rdata.pop(0)
if isinstance(data, Exception): if isinstance(data, Exception):
raise data raise data
return data return data
def send(self, data): def send(self, data):
if self.peer_address is None or not self._connected: if self.peer_address is None or not self._connected:
raise socket.error(32, 'Broken pipe') raise OSError(32, 'Broken pipe')
self._check_err('send') self._check_err('send')
self.sdata.append(data) self.sdata.append(data)
@pytest.fixture() @pytest.fixture()
def virtual_socket(monkeypatch): def virtual_socket(monkeypatch):
vsocket = _VirtualSocket() vsocket = _VirtualSocket()
gsock = vsocket.gsock gsock = vsocket.gsock
monkeypatch.setattr(socket, 'create_connection', gsock.create_connection) monkeypatch.setattr(socket, 'create_connection', gsock.create_connection)
monkeypatch.setattr(socket, 'socket', gsock.socket) monkeypatch.setattr(socket, 'socket', gsock.socket)
monkeypatch.setattr(socket, 'getaddrinfo', gsock.getaddrinfo) monkeypatch.setattr(socket, 'getaddrinfo', gsock.getaddrinfo)
return vsocket return vsocket

View File

@ -22,10 +22,11 @@
(key) ecdsa-sha2-nistp256 -- [fail] using weak elliptic curves (key) ecdsa-sha2-nistp256 -- [fail] using weak elliptic curves
 `- [warn] using weak random number generator could reveal the key  `- [warn] using weak random number generator could reveal the key
`- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 `- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62
(key) ssh-rsa (1024-bit) -- [fail] using small 1024-bit modulus (key) ssh-rsa (1024-bit) -- [fail] using weak hashing algorithm
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28
(key) ssh-dss -- [fail] removed (in server) and disabled (in client) since OpenSSH 7.0, weak algorithm
 `- [warn] using small 1024-bit modulus  `- [warn] using small 1024-bit modulus
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28
(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  `- [warn] using weak random number generator could reveal the key
`- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28 `- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28
@ -63,7 +64,6 @@
(fin) ssh-rsa: SHA256:CDfAU12pjQS7/91kg7gYacza0U/6PDbE04Ic3IpYxkM (fin) ssh-rsa: SHA256:CDfAU12pjQS7/91kg7gYacza0U/6PDbE04Ic3IpYxkM
# algorithm recommendations (for Dropbear SSH 2019.78) # algorithm recommendations (for Dropbear SSH 2019.78)
(rec) !ssh-rsa -- key algorithm to change (increase modulus size to 2048 bits or larger) 
(rec) -3des-cbc -- enc algorithm to remove  (rec) -3des-cbc -- enc algorithm to remove 
(rec) -3des-ctr -- enc algorithm to remove  (rec) -3des-ctr -- enc algorithm to remove 
(rec) -aes128-cbc -- enc algorithm to remove  (rec) -aes128-cbc -- enc algorithm to remove 
@ -71,7 +71,6 @@
(rec) -ecdh-sha2-nistp256 -- kex algorithm to remove  (rec) -ecdh-sha2-nistp256 -- kex algorithm to remove 
(rec) -ecdh-sha2-nistp384 -- kex algorithm to remove  (rec) -ecdh-sha2-nistp384 -- kex algorithm to remove 
(rec) -ecdh-sha2-nistp521 -- 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) -hmac-sha1-96 -- mac algorithm to remove 
(rec) -ssh-dss -- key algorithm to remove  (rec) -ssh-dss -- key algorithm to remove 
(rec) +diffie-hellman-group16-sha512 -- kex algorithm to append  (rec) +diffie-hellman-group16-sha512 -- kex algorithm to append 

View File

@ -31,17 +31,18 @@
`- [info] available since OpenSSH 2.3.0 `- [info] available since OpenSSH 2.3.0
(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm (kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53 `- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
(kex) diffie-hellman-group1-sha1 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm (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  `- [fail] disabled (in client) since OpenSSH 7.0, logjam attack
 `- [warn] using small 1024-bit modulus
 `- [warn] using weak hashing algorithm  `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28 `- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
# host-key algorithms # host-key algorithms
(key) ssh-rsa (1024-bit) -- [fail] using small 1024-bit modulus (key) ssh-rsa (1024-bit) -- [fail] using weak hashing algorithm
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28
(key) ssh-dss -- [fail] removed (in server) and disabled (in client) since OpenSSH 7.0, weak algorithm
 `- [warn] using small 1024-bit modulus  `- [warn] using small 1024-bit modulus
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28
(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  `- [warn] using weak random number generator could reveal the key
`- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28 `- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28
@ -116,7 +117,6 @@
(fin) ssh-rsa: SHA256:YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4 (fin) ssh-rsa: SHA256:YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4
# algorithm recommendations (for OpenSSH 4.0) # algorithm recommendations (for OpenSSH 4.0)
(rec) !ssh-rsa -- key algorithm to change (increase modulus size to 2048 bits or larger) 
(rec) -3des-cbc -- enc algorithm to remove  (rec) -3des-cbc -- enc algorithm to remove 
(rec) -aes128-cbc -- enc algorithm to remove  (rec) -aes128-cbc -- enc algorithm to remove 
(rec) -aes192-cbc -- enc algorithm to remove  (rec) -aes192-cbc -- enc algorithm to remove 

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

@ -25,17 +25,18 @@
`- [info] available since OpenSSH 2.3.0 `- [info] available since OpenSSH 2.3.0
(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm (kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53 `- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
(kex) diffie-hellman-group1-sha1 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm (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  `- [fail] disabled (in client) since OpenSSH 7.0, logjam attack
 `- [warn] using small 1024-bit modulus
 `- [warn] using weak hashing algorithm  `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28 `- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
# host-key algorithms # host-key algorithms
(key) ssh-rsa (1024-bit) -- [fail] using small 1024-bit modulus (key) ssh-rsa (1024-bit) -- [fail] using weak hashing algorithm
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28
(key) ssh-dss -- [fail] removed (in server) and disabled (in client) since OpenSSH 7.0, weak algorithm
 `- [warn] using small 1024-bit modulus  `- [warn] using small 1024-bit modulus
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28
(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  `- [warn] using weak random number generator could reveal the key
`- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28 `- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28
@ -122,7 +123,6 @@
# algorithm recommendations (for OpenSSH 5.6) # 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) !diffie-hellman-group-exchange-sha256 -- kex algorithm to change (increase modulus size to 2048 bits or larger) 
(rec) !ssh-rsa -- key algorithm to change (increase modulus size to 2048 bits or larger) 
(rec) -3des-cbc -- enc algorithm to remove  (rec) -3des-cbc -- enc algorithm to remove 
(rec) -aes128-cbc -- enc algorithm to remove  (rec) -aes128-cbc -- enc algorithm to remove 
(rec) -aes192-cbc -- enc algorithm to remove  (rec) -aes192-cbc -- enc algorithm to remove 

View File

@ -25,14 +25,15 @@
`- [info] available since OpenSSH 2.3.0 `- [info] available since OpenSSH 2.3.0
(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm (kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53 `- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
(kex) diffie-hellman-group1-sha1 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm (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  `- [fail] disabled (in client) since OpenSSH 7.0, logjam attack
 `- [warn] using small 1024-bit modulus
 `- [warn] using weak hashing algorithm  `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28 `- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
# host-key algorithms # host-key algorithms
(key) ssh-rsa (1024-bit) -- [fail] using small 1024-bit modulus (key) ssh-rsa (1024-bit) -- [fail] using weak hashing algorithm
 `- [warn] using small 1024-bit modulus
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28 `- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28
(key) ssh-rsa-cert-v01@openssh.com (1024-bit cert/1024-bit CA) -- [fail] using small 1024-bit modulus (key) ssh-rsa-cert-v01@openssh.com (1024-bit cert/1024-bit CA) -- [fail] using small 1024-bit modulus
`- [info] available since OpenSSH 5.6 `- [info] available since OpenSSH 5.6
@ -120,7 +121,6 @@
# algorithm recommendations (for OpenSSH 5.6) # 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) !diffie-hellman-group-exchange-sha256 -- kex algorithm to change (increase modulus size to 2048 bits or larger) 
(rec) !ssh-rsa -- key algorithm to change (increase modulus size to 2048 bits or larger) 
(rec) !ssh-rsa-cert-v01@openssh.com -- key algorithm to change (increase modulus size to 2048 bits or larger)  (rec) !ssh-rsa-cert-v01@openssh.com -- key algorithm to change (increase modulus size to 2048 bits or larger) 
(rec) -3des-cbc -- enc algorithm to remove  (rec) -3des-cbc -- enc algorithm to remove 
(rec) -aes128-cbc -- enc algorithm to remove  (rec) -aes128-cbc -- enc algorithm to remove 
@ -139,6 +139,7 @@
(rec) -hmac-ripemd160@openssh.com -- mac algorithm to remove  (rec) -hmac-ripemd160@openssh.com -- mac algorithm to remove 
(rec) -hmac-sha1-96 -- mac algorithm to remove  (rec) -hmac-sha1-96 -- mac algorithm to remove 
(rec) -rijndael-cbc@lysator.liu.se -- enc algorithm to remove  (rec) -rijndael-cbc@lysator.liu.se -- enc algorithm to remove 
(rec) -ssh-rsa -- key algorithm to remove 
(rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove  (rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove 
# additional info # additional info

View File

@ -25,14 +25,15 @@
`- [info] available since OpenSSH 2.3.0 `- [info] available since OpenSSH 2.3.0
(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm (kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53 `- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
(kex) diffie-hellman-group1-sha1 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm (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  `- [fail] disabled (in client) since OpenSSH 7.0, logjam attack
 `- [warn] using small 1024-bit modulus
 `- [warn] using weak hashing algorithm  `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28 `- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
# host-key algorithms # host-key algorithms
(key) ssh-rsa (1024-bit) -- [fail] using small 1024-bit modulus (key) ssh-rsa (1024-bit) -- [fail] using weak hashing algorithm
 `- [warn] using small 1024-bit modulus
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28 `- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28
(key) ssh-rsa-cert-v01@openssh.com (1024-bit cert/3072-bit CA) -- [fail] using small 1024-bit modulus (key) ssh-rsa-cert-v01@openssh.com (1024-bit cert/3072-bit CA) -- [fail] using small 1024-bit modulus
`- [info] available since OpenSSH 5.6 `- [info] available since OpenSSH 5.6
@ -120,7 +121,6 @@
# algorithm recommendations (for OpenSSH 5.6) # 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) !diffie-hellman-group-exchange-sha256 -- kex algorithm to change (increase modulus size to 2048 bits or larger) 
(rec) !ssh-rsa -- key algorithm to change (increase modulus size to 2048 bits or larger) 
(rec) !ssh-rsa-cert-v01@openssh.com -- key algorithm to change (increase modulus size to 2048 bits or larger)  (rec) !ssh-rsa-cert-v01@openssh.com -- key algorithm to change (increase modulus size to 2048 bits or larger) 
(rec) -3des-cbc -- enc algorithm to remove  (rec) -3des-cbc -- enc algorithm to remove 
(rec) -aes128-cbc -- enc algorithm to remove  (rec) -aes128-cbc -- enc algorithm to remove 
@ -139,6 +139,7 @@
(rec) -hmac-ripemd160@openssh.com -- mac algorithm to remove  (rec) -hmac-ripemd160@openssh.com -- mac algorithm to remove 
(rec) -hmac-sha1-96 -- mac algorithm to remove  (rec) -hmac-sha1-96 -- mac algorithm to remove 
(rec) -rijndael-cbc@lysator.liu.se -- enc algorithm to remove  (rec) -rijndael-cbc@lysator.liu.se -- enc algorithm to remove 
(rec) -ssh-rsa -- key algorithm to remove 
(rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove  (rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove 
# additional info # additional info

View File

@ -25,14 +25,15 @@
`- [info] available since OpenSSH 2.3.0 `- [info] available since OpenSSH 2.3.0
(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm (kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53 `- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
(kex) diffie-hellman-group1-sha1 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm (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  `- [fail] disabled (in client) since OpenSSH 7.0, logjam attack
 `- [warn] using small 1024-bit modulus
 `- [warn] using weak hashing algorithm  `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28 `- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
# host-key algorithms # host-key algorithms
(key) ssh-rsa (3072-bit) -- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28 (key) ssh-rsa (3072-bit) -- [fail] using weak hashing algorithm
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28
(key) ssh-rsa-cert-v01@openssh.com (3072-bit cert/1024-bit CA) -- [fail] using small 1024-bit modulus (key) ssh-rsa-cert-v01@openssh.com (3072-bit cert/1024-bit CA) -- [fail] using small 1024-bit modulus
`- [info] available since OpenSSH 5.6 `- [info] available since OpenSSH 5.6
@ -137,6 +138,7 @@
(rec) -hmac-ripemd160@openssh.com -- mac algorithm to remove  (rec) -hmac-ripemd160@openssh.com -- mac algorithm to remove 
(rec) -hmac-sha1-96 -- mac algorithm to remove  (rec) -hmac-sha1-96 -- mac algorithm to remove 
(rec) -rijndael-cbc@lysator.liu.se -- enc algorithm to remove  (rec) -rijndael-cbc@lysator.liu.se -- enc algorithm to remove 
(rec) -ssh-rsa -- key algorithm to remove 
(rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove  (rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove 
# additional info # additional info

View File

@ -25,14 +25,15 @@
`- [info] available since OpenSSH 2.3.0 `- [info] available since OpenSSH 2.3.0
(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm (kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53 `- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
(kex) diffie-hellman-group1-sha1 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm (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  `- [fail] disabled (in client) since OpenSSH 7.0, logjam attack
 `- [warn] using small 1024-bit modulus
 `- [warn] using weak hashing algorithm  `- [warn] using weak hashing algorithm
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28 `- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
# host-key algorithms # host-key algorithms
(key) ssh-rsa (3072-bit) -- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28 (key) ssh-rsa (3072-bit) -- [fail] using weak hashing algorithm
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28
(key) ssh-rsa-cert-v01@openssh.com (3072-bit cert/3072-bit CA) -- [info] available since OpenSSH 5.6 (key) ssh-rsa-cert-v01@openssh.com (3072-bit cert/3072-bit CA) -- [info] available since OpenSSH 5.6
# encryption algorithms (ciphers) # encryption algorithms (ciphers)
@ -135,6 +136,7 @@
(rec) -hmac-ripemd160@openssh.com -- mac algorithm to remove  (rec) -hmac-ripemd160@openssh.com -- mac algorithm to remove 
(rec) -hmac-sha1-96 -- mac algorithm to remove  (rec) -hmac-sha1-96 -- mac algorithm to remove 
(rec) -rijndael-cbc@lysator.liu.se -- enc algorithm to remove  (rec) -rijndael-cbc@lysator.liu.se -- enc algorithm to remove 
(rec) -ssh-rsa -- key algorithm to remove 
(rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove  (rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove 
# additional info # additional info

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

View File

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

View File

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

View File

@ -0,0 +1 @@
{"errors": [{"actual": ["2048"], "expected_optional": [""], "expected_required": ["4096"], "mismatched_field": "Group exchange (diffie-hellman-group-exchange-sha256) modulus sizes"}], "host": "localhost", "passed": false, "policy": "Docker policy: test14 (version 1)"}

View File

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

Errors:
* Group exchange (diffie-hellman-group-exchange-sha256) modulus sizes did not match. Expected: 4096; Actual: 2048

View File

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

View File

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

View File

@ -23,7 +23,8 @@
# host-key algorithms # host-key algorithms
(key) rsa-sha2-512 (3072-bit) -- [info] available since OpenSSH 7.2 (key) rsa-sha2-512 (3072-bit) -- [info] available since OpenSSH 7.2
(key) rsa-sha2-256 (3072-bit) -- [info] available since OpenSSH 7.2 (key) rsa-sha2-256 (3072-bit) -- [info] available since OpenSSH 7.2
(key) ssh-rsa (3072-bit) -- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28 (key) ssh-rsa (3072-bit) -- [fail] using weak hashing algorithm
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28
(key) ecdsa-sha2-nistp256 -- [fail] using weak elliptic curves (key) ecdsa-sha2-nistp256 -- [fail] using weak elliptic curves
 `- [warn] using weak random number generator could reveal the key  `- [warn] using weak random number generator could reveal the key
`- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 `- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62
@ -68,6 +69,7 @@
(rec) -ecdh-sha2-nistp384 -- kex algorithm to remove  (rec) -ecdh-sha2-nistp384 -- kex algorithm to remove 
(rec) -ecdh-sha2-nistp521 -- kex algorithm to remove  (rec) -ecdh-sha2-nistp521 -- kex algorithm to remove 
(rec) -ecdsa-sha2-nistp256 -- key algorithm to remove  (rec) -ecdsa-sha2-nistp256 -- key algorithm to remove 
(rec) -ssh-rsa -- key algorithm to remove 
(rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove  (rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove 
(rec) -hmac-sha1 -- mac algorithm to remove  (rec) -hmac-sha1 -- mac algorithm to remove 
(rec) -hmac-sha1-etm@openssh.com -- mac algorithm to remove  (rec) -hmac-sha1-etm@openssh.com -- mac algorithm to remove 

View File

@ -63,7 +63,6 @@
(rec) -ecdh-sha2-nistp521 -- kex algorithm to remove  (rec) -ecdh-sha2-nistp521 -- kex algorithm to remove 
(rec) +rsa-sha2-256 -- key algorithm to append  (rec) +rsa-sha2-256 -- key algorithm to append 
(rec) +rsa-sha2-512 -- key algorithm to append  (rec) +rsa-sha2-512 -- key algorithm to append 
(rec) +ssh-rsa -- key algorithm to append 
(rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove  (rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove 
(rec) -hmac-sha1 -- mac algorithm to remove  (rec) -hmac-sha1 -- mac algorithm to remove 
(rec) -hmac-sha1-etm@openssh.com -- mac algorithm to remove  (rec) -hmac-sha1-etm@openssh.com -- mac algorithm to remove 

View File

@ -35,5 +35,4 @@
(rec) +diffie-hellman-group18-sha512 -- kex algorithm to append  (rec) +diffie-hellman-group18-sha512 -- kex algorithm to append 
(rec) +rsa-sha2-256 -- key algorithm to append  (rec) +rsa-sha2-256 -- key algorithm to append 
(rec) +rsa-sha2-512 -- key algorithm to append  (rec) +rsa-sha2-512 -- key algorithm to append 
(rec) +ssh-rsa -- key algorithm to append 

View File

@ -0,0 +1,10 @@
#
# Docker policy: test1
#
name = "Docker policy: test1"
version = 1
host keys = ssh-rsa, ssh-dss
key exchanges = diffie-hellman-group-exchange-sha256, diffie-hellman-group-exchange-sha1, diffie-hellman-group14-sha1, diffie-hellman-group1-sha1
ciphers = 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
macs = 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,39 @@
#
# Docker policy: test10
#
# The name of this policy (displayed in the output during scans). Must be in quotes.
name = "Docker poliicy: test10"
# 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 = "SSH-2.0-OpenSSH_5.6"
# The header that must match exactly. Commented out to ignore headers, since variability in the header is sometimes normal.
# header = "[]"
# The compression options that must match exactly (order matters). Commented out to ignore by default.
# compressions = none, zlib@openssh.com
# RSA host key sizes.
hostkey_size_rsa-sha2-256 = 3072
hostkey_size_rsa-sha2-512 = 3072
hostkey_size_ssh-rsa = 3072
hostkey_size_ssh-rsa-cert-v01@openssh.com = 4096
# RSA CA key sizes.
cakey_size_ssh-rsa-cert-v01@openssh.com = 4096
# The host key types that must match exactly (order matters).
host keys = ssh-rsa, ssh-rsa-cert-v01@openssh.com
# The key exchange algorithms that must match exactly (order matters).
key exchanges = diffie-hellman-group-exchange-sha256, diffie-hellman-group-exchange-sha1, diffie-hellman-group14-sha1, diffie-hellman-group1-sha1
# The ciphers that must match exactly (order matters).
ciphers = 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
# The MACs that must match exactly (order matters).
macs = 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,35 @@
#
# Docker policy: test11
#
# The name of this policy (displayed in the output during scans). Must be in quotes.
name = "Docker policy: test11"
# 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 = "SSH-2.0-OpenSSH_8.0"
# The header that must match exactly. Commented out to ignore headers, since variability in the header is sometimes normal.
# header = "[]"
# The compression options that must match exactly (order matters). Commented out to ignore by default.
# compressions = none, zlib@openssh.com
# RSA host key sizes.
hostkey_size_rsa-sha2-256 = 3072
hostkey_size_rsa-sha2-512 = 3072
hostkey_size_ssh-rsa = 3072
# The host key types that must match exactly (order matters).
host keys = rsa-sha2-512, rsa-sha2-256, ssh-rsa, ecdsa-sha2-nistp256, ssh-ed25519
# The key exchange algorithms that must match exactly (order matters).
key exchanges = 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, diffie-hellman-group14-sha1
# The ciphers that must match exactly (order matters).
ciphers = chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com
# The MACs that must match exactly (order matters).
macs = 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,35 @@
#
# Docker policy: test12
#
# The name of this policy (displayed in the output during scans). Must be in quotes.
name = "Docker policy: test12"
# 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 = "SSH-2.0-OpenSSH_8.0"
# The header that must match exactly. Commented out to ignore headers, since variability in the header is sometimes normal.
# header = "[]"
# The compression options that must match exactly (order matters). Commented out to ignore by default.
# compressions = none, zlib@openssh.com
# RSA host key sizes.
hostkey_size_rsa-sha2-256 = 4096
hostkey_size_rsa-sha2-512 = 4096
hostkey_size_ssh-rsa = 4096
# The host key types that must match exactly (order matters).
host keys = rsa-sha2-512, rsa-sha2-256, ssh-rsa, ecdsa-sha2-nistp256, ssh-ed25519
# The key exchange algorithms that must match exactly (order matters).
key exchanges = 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, diffie-hellman-group14-sha1
# The ciphers that must match exactly (order matters).
ciphers = chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com
# The MACs that must match exactly (order matters).
macs = 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,38 @@
#
# Docker policy: test13
#
# The name of this policy (displayed in the output during scans). Must be in quotes.
name = "Docker policy: test13"
# 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 = "SSH-2.0-OpenSSH_8.0"
# The header that must match exactly. Commented out to ignore headers, since variability in the header is sometimes normal.
# header = "[]"
# The compression options that must match exactly (order matters). Commented out to ignore by default.
# compressions = none, zlib@openssh.com
# RSA host key sizes.
hostkey_size_rsa-sha2-256 = 3072
hostkey_size_rsa-sha2-512 = 3072
hostkey_size_ssh-rsa = 3072
# Group exchange DH modulus sizes.
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
# The host key types that must match exactly (order matters).
host keys = rsa-sha2-512, rsa-sha2-256, ssh-rsa, ecdsa-sha2-nistp256, ssh-ed25519
# The key exchange algorithms that must match exactly (order matters).
key exchanges = 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, diffie-hellman-group14-sha1
# The ciphers that must match exactly (order matters).
ciphers = chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com
# The MACs that must match exactly (order matters).
macs = 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,38 @@
#
# Docker policy: test14
#
# The name of this policy (displayed in the output during scans). Must be in quotes.
name = "Docker policy: test14"
# 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 = "SSH-2.0-OpenSSH_8.0"
# The header that must match exactly. Commented out to ignore headers, since variability in the header is sometimes normal.
# header = "[]"
# The compression options that must match exactly (order matters). Commented out to ignore by default.
# compressions = none, zlib@openssh.com
# RSA host key sizes.
hostkey_size_rsa-sha2-256 = 3072
hostkey_size_rsa-sha2-512 = 3072
hostkey_size_ssh-rsa = 3072
# Group exchange DH modulus sizes.
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 4096
# The host key types that must match exactly (order matters).
host keys = rsa-sha2-512, rsa-sha2-256, ssh-rsa, ecdsa-sha2-nistp256, ssh-ed25519
# The key exchange algorithms that must match exactly (order matters).
key exchanges = 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, diffie-hellman-group14-sha1
# The ciphers that must match exactly (order matters).
ciphers = chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com
# The MACs that must match exactly (order matters).
macs = 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,10 @@
#
# Docker policy: test2
#
name = "Docker policy: test2"
version = 1
host keys = ssh-rsa, ssh-dss
key exchanges = kex_alg1, kex_alg2
ciphers = 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
macs = 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,10 @@
#
# Docker policy: test3
#
name = "Docker policy: test3"
version = 1
host keys = ssh-rsa, ssh-dss, key_alg1
key exchanges = diffie-hellman-group-exchange-sha256, diffie-hellman-group-exchange-sha1, diffie-hellman-group14-sha1, diffie-hellman-group1-sha1
ciphers = 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
macs = 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,10 @@
#
# Docker policy: test4
#
name = "Docker policy: test4"
version = 1
host keys = ssh-rsa, ssh-dss
key exchanges = diffie-hellman-group-exchange-sha256, diffie-hellman-group-exchange-sha1, diffie-hellman-group14-sha1, diffie-hellman-group1-sha1
ciphers = cipher_alg1, cipher_alg2
macs = 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,10 @@
#
# Docker policy: test5
#
name = "Docker policy: test5"
version = 1
host keys = ssh-rsa, ssh-dss
key exchanges = diffie-hellman-group-exchange-sha256, diffie-hellman-group-exchange-sha1, diffie-hellman-group14-sha1, diffie-hellman-group1-sha1
ciphers = 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
macs = hmac-md5, hmac-sha1, umac-64@openssh.com, hmac-ripemd160, hmac-ripemd160@openssh.com, hmac_alg1, hmac-md5-96

View File

@ -0,0 +1,12 @@
#
# Docker policy: test6
#
name = "Docker policy: test6"
version = 1
banner = "SSH-2.0-OpenSSH_8.0"
compressions = none, zlib@openssh.com
host keys = rsa-sha2-512, rsa-sha2-256, ssh-rsa, ecdsa-sha2-nistp256, ssh-ed25519
key exchanges = 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, diffie-hellman-group14-sha1
ciphers = chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com
macs = 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,39 @@
#
# Docker policy: test7
#
# The name of this policy (displayed in the output during scans). Must be in quotes.
name = "Docker poliicy: test7"
# 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 = "SSH-2.0-OpenSSH_5.6"
# The header that must match exactly. Commented out to ignore headers, since variability in the header is sometimes normal.
# header = "[]"
# The compression options that must match exactly (order matters). Commented out to ignore by default.
# compressions = none, zlib@openssh.com
# RSA host key sizes.
hostkey_size_rsa-sha2-256 = 3072
hostkey_size_rsa-sha2-512 = 3072
hostkey_size_ssh-rsa = 3072
hostkey_size_ssh-rsa-cert-v01@openssh.com = 3072
# RSA CA key sizes.
cakey_size_ssh-rsa-cert-v01@openssh.com = 1024
# The host key types that must match exactly (order matters).
host keys = ssh-rsa, ssh-rsa-cert-v01@openssh.com
# The key exchange algorithms that must match exactly (order matters).
key exchanges = diffie-hellman-group-exchange-sha256, diffie-hellman-group-exchange-sha1, diffie-hellman-group14-sha1, diffie-hellman-group1-sha1
# The ciphers that must match exactly (order matters).
ciphers = 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
# The MACs that must match exactly (order matters).
macs = 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,39 @@
#
# Docker policy: test8
#
# The name of this policy (displayed in the output during scans). Must be in quotes.
name = "Docker poliicy: test8"
# 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 = "SSH-2.0-OpenSSH_5.6"
# The header that must match exactly. Commented out to ignore headers, since variability in the header is sometimes normal.
# header = "[]"
# The compression options that must match exactly (order matters). Commented out to ignore by default.
# compressions = none, zlib@openssh.com
# RSA host key sizes.
hostkey_size_rsa-sha2-256 = 3072
hostkey_size_rsa-sha2-512 = 3072
hostkey_size_ssh-rsa = 3072
hostkey_size_ssh-rsa-cert-v01@openssh.com = 3072
# RSA CA key sizes.
cakey_size_ssh-rsa-cert-v01@openssh.com = 2048
# The host key types that must match exactly (order matters).
host keys = ssh-rsa, ssh-rsa-cert-v01@openssh.com
# The key exchange algorithms that must match exactly (order matters).
key exchanges = diffie-hellman-group-exchange-sha256, diffie-hellman-group-exchange-sha1, diffie-hellman-group14-sha1, diffie-hellman-group1-sha1
# The ciphers that must match exactly (order matters).
ciphers = 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
# The MACs that must match exactly (order matters).
macs = 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,39 @@
#
# Docker policy: test9
#
# The name of this policy (displayed in the output during scans). Must be in quotes.
name = "Docker poliicy: test9"
# 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 = "SSH-2.0-OpenSSH_5.6"
# The header that must match exactly. Commented out to ignore headers, since variability in the header is sometimes normal.
# header = "[]"
# The compression options that must match exactly (order matters). Commented out to ignore by default.
# compressions = none, zlib@openssh.com
# RSA host key sizes.
hostkey_size_rsa-sha2-256 = 3072
hostkey_size_rsa-sha2-512 = 3072
hostkey_size_ssh-rsa = 3072
hostkey_size_ssh-rsa-cert-v01@openssh.com = 4096
# RSA CA key sizes.
cakey_size_ssh-rsa-cert-v01@openssh.com = 1024
# The host key types that must match exactly (order matters).
host keys = ssh-rsa, ssh-rsa-cert-v01@openssh.com
# The key exchange algorithms that must match exactly (order matters).
key exchanges = diffie-hellman-group-exchange-sha256, diffie-hellman-group-exchange-sha1, diffie-hellman-group14-sha1, diffie-hellman-group1-sha1
# The ciphers that must match exactly (order matters).
ciphers = 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
# The MACs that must match exactly (order matters).
macs = hmac-md5, hmac-sha1, umac-64@openssh.com, hmac-ripemd160, hmac-ripemd160@openssh.com, hmac-sha1-96, hmac-md5-96

View File

@ -1,200 +1,196 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pytest import pytest
# pylint: disable=attribute-defined-outside-init # pylint: disable=attribute-defined-outside-init
class TestAuditConf(object): class TestAuditConf:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def init(self, ssh_audit): def init(self, ssh_audit):
self.AuditConf = ssh_audit.AuditConf self.AuditConf = ssh_audit.AuditConf
self.usage = ssh_audit.usage self.usage = ssh_audit.usage
@staticmethod @staticmethod
def _test_conf(conf, **kwargs): def _test_conf(conf, **kwargs):
options = { options = {
'host': None, 'host': '',
'port': 22, 'port': 22,
'ssh1': True, 'ssh1': True,
'ssh2': True, 'ssh2': True,
'batch': False, 'batch': False,
'colors': True, 'colors': True,
'verbose': False, 'verbose': False,
'level': 'info', 'level': 'info',
'ipv4': True, 'ipv4': True,
'ipv6': True, 'ipv6': True,
'ipvo': () 'ipvo': ()
} }
for k, v in kwargs.items(): for k, v in kwargs.items():
options[k] = v options[k] = v
assert conf.host == options['host'] assert conf.host == options['host']
assert conf.port == options['port'] assert conf.port == options['port']
assert conf.ssh1 is options['ssh1'] assert conf.ssh1 is options['ssh1']
assert conf.ssh2 is options['ssh2'] assert conf.ssh2 is options['ssh2']
assert conf.batch is options['batch'] assert conf.batch is options['batch']
assert conf.colors is options['colors'] assert conf.colors is options['colors']
assert conf.verbose is options['verbose'] assert conf.verbose is options['verbose']
assert conf.level == options['level'] assert conf.level == options['level']
assert conf.ipv4 == options['ipv4'] assert conf.ipv4 == options['ipv4']
assert conf.ipv6 == options['ipv6'] assert conf.ipv6 == options['ipv6']
assert conf.ipvo == options['ipvo'] assert conf.ipvo == options['ipvo']
def test_audit_conf_defaults(self): def test_audit_conf_defaults(self):
conf = self.AuditConf() conf = self.AuditConf()
self._test_conf(conf) self._test_conf(conf)
def test_audit_conf_booleans(self): def test_audit_conf_booleans(self):
conf = self.AuditConf() conf = self.AuditConf()
for p in ['ssh1', 'ssh2', 'batch', 'colors', 'verbose']: for p in ['ssh1', 'ssh2', 'batch', 'colors', 'verbose']:
for v in [True, 1]: for v in [True, 1]:
setattr(conf, p, v) setattr(conf, p, v)
assert getattr(conf, p) is True assert getattr(conf, p) is True
for v in [False, 0]: for v in [False, 0]:
setattr(conf, p, v) setattr(conf, p, v)
assert getattr(conf, p) is False assert getattr(conf, p) is False
def test_audit_conf_port(self): def test_audit_conf_port(self):
conf = self.AuditConf() conf = self.AuditConf()
for port in [22, 2222]: for port in [22, 2222]:
conf.port = port conf.port = port
assert conf.port == port assert conf.port == port
for port in [-1, 0, 65536, 99999]: for port in [-1, 0, 65536, 99999]:
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError) as excinfo:
conf.port = port conf.port = port
excinfo.match(r'.*invalid port.*') excinfo.match(r'.*invalid port.*')
def test_audit_conf_ipvo(self): def test_audit_conf_ipvo(self):
# ipv4-only # ipv4-only
conf = self.AuditConf() conf = self.AuditConf()
conf.ipv4 = True conf.ipv4 = True
assert conf.ipv4 is True assert conf.ipv4 is True
assert conf.ipv6 is False assert conf.ipv6 is False
assert conf.ipvo == (4,) assert conf.ipvo == (4,)
# ipv6-only # ipv6-only
conf = self.AuditConf() conf = self.AuditConf()
conf.ipv6 = True conf.ipv6 = True
assert conf.ipv4 is False assert conf.ipv4 is False
assert conf.ipv6 is True assert conf.ipv6 is True
assert conf.ipvo == (6,) assert conf.ipvo == (6,)
# ipv4-only (by removing ipv6) # ipv4-only (by removing ipv6)
conf = self.AuditConf() conf = self.AuditConf()
conf.ipv6 = False conf.ipv6 = False
assert conf.ipv4 is True assert conf.ipv4 is True
assert conf.ipv6 is False assert conf.ipv6 is False
assert conf.ipvo == (4, ) assert conf.ipvo == (4, )
# ipv6-only (by removing ipv4) # ipv6-only (by removing ipv4)
conf = self.AuditConf() conf = self.AuditConf()
conf.ipv4 = False conf.ipv4 = False
assert conf.ipv4 is False assert conf.ipv4 is False
assert conf.ipv6 is True assert conf.ipv6 is True
assert conf.ipvo == (6, ) assert conf.ipvo == (6, )
# ipv4-preferred # ipv4-preferred
conf = self.AuditConf() conf = self.AuditConf()
conf.ipv4 = True conf.ipv4 = True
conf.ipv6 = True conf.ipv6 = True
assert conf.ipv4 is True assert conf.ipv4 is True
assert conf.ipv6 is True assert conf.ipv6 is True
assert conf.ipvo == (4, 6) assert conf.ipvo == (4, 6)
# ipv6-preferred # ipv6-preferred
conf = self.AuditConf() conf = self.AuditConf()
conf.ipv6 = True conf.ipv6 = True
conf.ipv4 = True conf.ipv4 = True
assert conf.ipv4 is True assert conf.ipv4 is True
assert conf.ipv6 is True assert conf.ipv6 is True
assert conf.ipvo == (6, 4) assert conf.ipvo == (6, 4)
# ipvo empty # ipvo empty
conf = self.AuditConf() conf = self.AuditConf()
conf.ipvo = () conf.ipvo = ()
assert conf.ipv4 is True assert conf.ipv4 is True
assert conf.ipv6 is True assert conf.ipv6 is True
assert conf.ipvo == () assert conf.ipvo == ()
# ipvo validation # ipvo validation
conf = self.AuditConf() conf = self.AuditConf()
conf.ipvo = (1, 2, 3, 4, 5, 6) conf.ipvo = (1, 2, 3, 4, 5, 6)
assert conf.ipvo == (4, 6) assert conf.ipvo == (4, 6)
conf.ipvo = (4, 4, 4, 6, 6) conf.ipvo = (4, 4, 4, 6, 6)
assert conf.ipvo == (4, 6) assert conf.ipvo == (4, 6)
def test_audit_conf_level(self): def test_audit_conf_level(self):
conf = self.AuditConf() conf = self.AuditConf()
for level in ['info', 'warn', 'fail']: for level in ['info', 'warn', 'fail']:
conf.level = level conf.level = level
assert conf.level == level assert conf.level == level
for level in ['head', 'good', 'unknown', None]: for level in ['head', 'good', 'unknown', None]:
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError) as excinfo:
conf.level = level conf.level = level
excinfo.match(r'.*invalid level.*') excinfo.match(r'.*invalid level.*')
def test_audit_conf_cmdline(self): def test_audit_conf_cmdline(self):
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
c = lambda x: self.AuditConf.from_cmdline(x.split(), self.usage) # noqa c = lambda x: self.AuditConf.from_cmdline(x.split(), self.usage) # noqa
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
conf = c('') conf = c('')
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
conf = c('-x') conf = c('-x')
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
conf = c('-h') conf = c('-h')
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
conf = c('--help') conf = c('--help')
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
conf = c(':') conf = c(':')
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
conf = c(':22') conf = c(':22')
conf = c('localhost') conf = c('localhost')
self._test_conf(conf, host='localhost') self._test_conf(conf, host='localhost')
conf = c('github.com') conf = c('github.com')
self._test_conf(conf, host='github.com') self._test_conf(conf, host='github.com')
conf = c('localhost:2222') conf = c('localhost:2222')
self._test_conf(conf, host='localhost', port=2222) self._test_conf(conf, host='localhost', port=2222)
conf = c('-p 2222 localhost') conf = c('-p 2222 localhost')
self._test_conf(conf, host='localhost', port=2222) self._test_conf(conf, host='localhost', port=2222)
conf = c('2001:4860:4860::8888') conf = c('2001:4860:4860::8888')
self._test_conf(conf, host='2001:4860:4860::8888') self._test_conf(conf, host='2001:4860:4860::8888')
conf = c('[2001:4860:4860::8888]:22') conf = c('[2001:4860:4860::8888]:22')
self._test_conf(conf, host='2001:4860:4860::8888') self._test_conf(conf, host='2001:4860:4860::8888')
conf = c('[2001:4860:4860::8888]:2222') conf = c('[2001:4860:4860::8888]:2222')
self._test_conf(conf, host='2001:4860:4860::8888', port=2222) self._test_conf(conf, host='2001:4860:4860::8888', port=2222)
conf = c('-p 2222 2001:4860:4860::8888') conf = c('-p 2222 2001:4860:4860::8888')
self._test_conf(conf, host='2001:4860:4860::8888', port=2222) self._test_conf(conf, host='2001:4860:4860::8888', port=2222)
with pytest.raises(SystemExit): with pytest.raises(ValueError):
conf = c('localhost:') conf = c('localhost:abc')
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
conf = c('localhost:abc') conf = c('-p abc localhost')
with pytest.raises(SystemExit): with pytest.raises(ValueError):
conf = c('-p abc localhost') conf = c('localhost:-22')
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
conf = c('localhost:-22') conf = c('-p -22 localhost')
with pytest.raises(SystemExit): with pytest.raises(ValueError):
conf = c('-p -22 localhost') conf = c('localhost:99999')
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
conf = c('localhost:99999') conf = c('-p 99999 localhost')
with pytest.raises(SystemExit): conf = c('-1 localhost')
conf = c('-p 99999 localhost') self._test_conf(conf, host='localhost', ssh1=True, ssh2=False)
conf = c('-1 localhost') conf = c('-2 localhost')
self._test_conf(conf, host='localhost', ssh1=True, ssh2=False) self._test_conf(conf, host='localhost', ssh1=False, ssh2=True)
conf = c('-2 localhost') conf = c('-12 localhost')
self._test_conf(conf, host='localhost', ssh1=False, ssh2=True) self._test_conf(conf, host='localhost', ssh1=True, ssh2=True)
conf = c('-12 localhost') conf = c('-4 localhost')
self._test_conf(conf, host='localhost', ssh1=True, ssh2=True) self._test_conf(conf, host='localhost', ipv4=True, ipv6=False, ipvo=(4,))
conf = c('-4 localhost') conf = c('-6 localhost')
self._test_conf(conf, host='localhost', ipv4=True, ipv6=False, ipvo=(4,)) self._test_conf(conf, host='localhost', ipv4=False, ipv6=True, ipvo=(6,))
conf = c('-6 localhost') conf = c('-46 localhost')
self._test_conf(conf, host='localhost', ipv4=False, ipv6=True, ipvo=(6,)) self._test_conf(conf, host='localhost', ipv4=True, ipv6=True, ipvo=(4, 6))
conf = c('-46 localhost') conf = c('-64 localhost')
self._test_conf(conf, host='localhost', ipv4=True, ipv6=True, ipvo=(4, 6)) self._test_conf(conf, host='localhost', ipv4=True, ipv6=True, ipvo=(6, 4))
conf = c('-64 localhost') conf = c('-b localhost')
self._test_conf(conf, host='localhost', ipv4=True, ipv6=True, ipvo=(6, 4)) self._test_conf(conf, host='localhost', batch=True, verbose=True)
conf = c('-b localhost') conf = c('-n localhost')
self._test_conf(conf, host='localhost', batch=True, verbose=True) self._test_conf(conf, host='localhost', colors=False)
conf = c('-n localhost') conf = c('-v localhost')
self._test_conf(conf, host='localhost', colors=False) self._test_conf(conf, host='localhost', verbose=True)
conf = c('-v localhost') conf = c('-l info localhost')
self._test_conf(conf, host='localhost', verbose=True) self._test_conf(conf, host='localhost', level='info')
conf = c('-l info localhost') conf = c('-l warn localhost')
self._test_conf(conf, host='localhost', level='info') self._test_conf(conf, host='localhost', level='warn')
conf = c('-l warn localhost') conf = c('-l fail localhost')
self._test_conf(conf, host='localhost', level='warn') self._test_conf(conf, host='localhost', level='fail')
conf = c('-l fail localhost') with pytest.raises(SystemExit):
self._test_conf(conf, host='localhost', level='fail') conf = c('-l something localhost')
with pytest.raises(SystemExit):
conf = c('-l something localhost')

View File

@ -1,69 +1,67 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pytest import pytest
# pylint: disable=line-too-long,attribute-defined-outside-init # pylint: disable=line-too-long,attribute-defined-outside-init
class TestBanner(object): class TestBanner:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def init(self, ssh_audit): def init(self, ssh_audit):
self.ssh = ssh_audit.SSH self.ssh = ssh_audit.SSH
def test_simple_banners(self): def test_simple_banners(self):
banner = lambda x: self.ssh.Banner.parse(x) # noqa banner = lambda x: self.ssh.Banner.parse(x) # noqa
b = banner('SSH-2.0-OpenSSH_7.3') b = banner('SSH-2.0-OpenSSH_7.3')
assert b.protocol == (2, 0) assert b.protocol == (2, 0)
assert b.software == 'OpenSSH_7.3' assert b.software == 'OpenSSH_7.3'
assert b.comments is None assert b.comments is None
assert str(b) == 'SSH-2.0-OpenSSH_7.3' assert str(b) == 'SSH-2.0-OpenSSH_7.3'
b = banner('SSH-1.99-Sun_SSH_1.1.3') b = banner('SSH-1.99-Sun_SSH_1.1.3')
assert b.protocol == (1, 99) assert b.protocol == (1, 99)
assert b.software == 'Sun_SSH_1.1.3' assert b.software == 'Sun_SSH_1.1.3'
assert b.comments is None assert b.comments is None
assert str(b) == 'SSH-1.99-Sun_SSH_1.1.3' assert str(b) == 'SSH-1.99-Sun_SSH_1.1.3'
b = banner('SSH-1.5-Cisco-1.25') b = banner('SSH-1.5-Cisco-1.25')
assert b.protocol == (1, 5) assert b.protocol == (1, 5)
assert b.software == 'Cisco-1.25' assert b.software == 'Cisco-1.25'
assert b.comments is None assert b.comments is None
assert str(b) == 'SSH-1.5-Cisco-1.25' assert str(b) == 'SSH-1.5-Cisco-1.25'
def test_invalid_banners(self): def test_invalid_banners(self):
b = lambda x: self.ssh.Banner.parse(x) # noqa b = lambda x: self.ssh.Banner.parse(x) # noqa
assert b('Something') is None assert b('Something') is None
assert b('SSH-XXX-OpenSSH_7.3') is None assert b('SSH-XXX-OpenSSH_7.3') is None
def test_banners_with_spaces(self): def test_banners_with_spaces(self):
b = lambda x: self.ssh.Banner.parse(x) # noqa b = lambda x: self.ssh.Banner.parse(x) # noqa
s = 'SSH-2.0-OpenSSH_4.3p2' s = 'SSH-2.0-OpenSSH_4.3p2'
assert str(b('SSH-2.0-OpenSSH_4.3p2 ')) == s assert str(b('SSH-2.0-OpenSSH_4.3p2 ')) == s
assert str(b('SSH-2.0- OpenSSH_4.3p2')) == s assert str(b('SSH-2.0- OpenSSH_4.3p2')) == s
assert str(b('SSH-2.0- OpenSSH_4.3p2 ')) == s assert str(b('SSH-2.0- OpenSSH_4.3p2 ')) == s
s = 'SSH-2.0-OpenSSH_4.3p2 Debian-9etch3 on i686-pc-linux-gnu' s = 'SSH-2.0-OpenSSH_4.3p2 Debian-9etch3 on i686-pc-linux-gnu'
assert str(b('SSH-2.0- OpenSSH_4.3p2 Debian-9etch3 on i686-pc-linux-gnu')) == s assert str(b('SSH-2.0- OpenSSH_4.3p2 Debian-9etch3 on i686-pc-linux-gnu')) == s
assert str(b('SSH-2.0-OpenSSH_4.3p2 Debian-9etch3 on i686-pc-linux-gnu ')) == s assert str(b('SSH-2.0-OpenSSH_4.3p2 Debian-9etch3 on i686-pc-linux-gnu ')) == s
assert str(b('SSH-2.0- OpenSSH_4.3p2 Debian-9etch3 on i686-pc-linux-gnu ')) == s assert str(b('SSH-2.0- OpenSSH_4.3p2 Debian-9etch3 on i686-pc-linux-gnu ')) == s
def test_banners_without_software(self): def test_banners_without_software(self):
b = lambda x: self.ssh.Banner.parse(x) # noqa b = lambda x: self.ssh.Banner.parse(x) # noqa
assert b('SSH-2.0').protocol == (2, 0) assert b('SSH-2.0').protocol == (2, 0)
assert b('SSH-2.0').software is None assert b('SSH-2.0').software is None
assert b('SSH-2.0').comments is None assert b('SSH-2.0').comments is None
assert str(b('SSH-2.0')) == 'SSH-2.0' assert str(b('SSH-2.0')) == 'SSH-2.0'
assert b('SSH-2.0-').protocol == (2, 0) assert b('SSH-2.0-').protocol == (2, 0)
assert b('SSH-2.0-').software == '' assert b('SSH-2.0-').software == ''
assert b('SSH-2.0-').comments is None assert b('SSH-2.0-').comments is None
assert str(b('SSH-2.0-')) == 'SSH-2.0-' assert str(b('SSH-2.0-')) == 'SSH-2.0-'
def test_banners_with_comments(self): def test_banners_with_comments(self):
b = lambda x: self.ssh.Banner.parse(x) # noqa b = lambda x: self.ssh.Banner.parse(x) # noqa
assert repr(b('SSH-2.0-OpenSSH_7.2p2 Ubuntu-1')) == '<Banner(protocol=2.0, software=OpenSSH_7.2p2, comments=Ubuntu-1)>' assert repr(b('SSH-2.0-OpenSSH_7.2p2 Ubuntu-1')) == '<Banner(protocol=2.0, software=OpenSSH_7.2p2, comments=Ubuntu-1)>'
assert repr(b('SSH-1.99-OpenSSH_3.4p1 Debian 1:3.4p1-1.woody.3')) == '<Banner(protocol=1.99, software=OpenSSH_3.4p1, comments=Debian 1:3.4p1-1.woody.3)>' assert repr(b('SSH-1.99-OpenSSH_3.4p1 Debian 1:3.4p1-1.woody.3')) == '<Banner(protocol=1.99, software=OpenSSH_3.4p1, comments=Debian 1:3.4p1-1.woody.3)>'
assert repr(b('SSH-1.5-1.3.7 F-SECURE SSH')) == '<Banner(protocol=1.5, software=1.3.7, comments=F-SECURE SSH)>' assert repr(b('SSH-1.5-1.3.7 F-SECURE SSH')) == '<Banner(protocol=1.5, software=1.3.7, comments=F-SECURE SSH)>'
def test_banners_with_multiple_protocols(self): def test_banners_with_multiple_protocols(self):
b = lambda x: self.ssh.Banner.parse(x) # noqa b = lambda x: self.ssh.Banner.parse(x) # noqa
assert str(b('SSH-1.99-SSH-1.99-OpenSSH_3.6.1p2')) == 'SSH-1.99-OpenSSH_3.6.1p2' assert str(b('SSH-1.99-SSH-1.99-OpenSSH_3.6.1p2')) == 'SSH-1.99-OpenSSH_3.6.1p2'
assert str(b('SSH-2.0-SSH-2.0-OpenSSH_4.3p2 Debian-9')) == 'SSH-2.0-OpenSSH_4.3p2 Debian-9' assert str(b('SSH-2.0-SSH-2.0-OpenSSH_4.3p2 Debian-9')) == 'SSH-2.0-OpenSSH_4.3p2 Debian-9'
assert str(b('SSH-1.99-SSH-2.0-dropbear_0.5')) == 'SSH-1.99-dropbear_0.5' assert str(b('SSH-1.99-SSH-2.0-dropbear_0.5')) == 'SSH-1.99-dropbear_0.5'
assert str(b('SSH-2.0-SSH-1.99-OpenSSH_4.2p1 SSH Secure Shell (non-commercial)')) == 'SSH-1.99-OpenSSH_4.2p1 SSH Secure Shell (non-commercial)' assert str(b('SSH-2.0-SSH-1.99-OpenSSH_4.2p1 SSH Secure Shell (non-commercial)')) == 'SSH-1.99-OpenSSH_4.2p1 SSH Secure Shell (non-commercial)'
assert str(b('SSH-1.99-SSH-1.99-SSH-1.99-OpenSSH_3.9p1')) == 'SSH-1.99-OpenSSH_3.9p1' assert str(b('SSH-1.99-SSH-1.99-SSH-1.99-OpenSSH_3.9p1')) == 'SSH-1.99-OpenSSH_3.9p1'

View File

@ -1,133 +1,131 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import re import re
import pytest import pytest
# pylint: disable=attribute-defined-outside-init,bad-whitespace # pylint: disable=attribute-defined-outside-init,bad-whitespace
class TestBuffer(object): class TestBuffer:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def init(self, ssh_audit): def init(self, ssh_audit):
self.rbuf = ssh_audit.ReadBuf self.rbuf = ssh_audit.ReadBuf
self.wbuf = ssh_audit.WriteBuf self.wbuf = ssh_audit.WriteBuf
self.utf8rchar = b'\xef\xbf\xbd' self.utf8rchar = b'\xef\xbf\xbd'
@classmethod @classmethod
def _b(cls, v): def _b(cls, v):
v = re.sub(r'\s', '', v) v = re.sub(r'\s', '', v)
data = [int(v[i * 2:i * 2 + 2], 16) for i in range(len(v) // 2)] data = [int(v[i * 2:i * 2 + 2], 16) for i in range(len(v) // 2)]
return bytes(bytearray(data)) return bytes(bytearray(data))
def test_unread(self): def test_unread(self):
w = self.wbuf().write_byte(1).write_int(2).write_flush() w = self.wbuf().write_byte(1).write_int(2).write_flush()
r = self.rbuf(w) r = self.rbuf(w)
assert r.unread_len == 5 assert r.unread_len == 5
r.read_byte() r.read_byte()
assert r.unread_len == 4 assert r.unread_len == 4
r.read_int() r.read_int()
assert r.unread_len == 0 assert r.unread_len == 0
def test_byte(self): def test_byte(self):
w = lambda x: self.wbuf().write_byte(x).write_flush() # noqa w = lambda x: self.wbuf().write_byte(x).write_flush() # noqa
r = lambda x: self.rbuf(x).read_byte() # noqa r = lambda x: self.rbuf(x).read_byte() # noqa
tc = [(0x00, '00'), tc = [(0x00, '00'),
(0x01, '01'), (0x01, '01'),
(0x10, '10'), (0x10, '10'),
(0xff, 'ff')] (0xff, 'ff')]
for p in tc: for p in tc:
assert w(p[0]) == self._b(p[1]) assert w(p[0]) == self._b(p[1])
assert r(self._b(p[1])) == p[0] assert r(self._b(p[1])) == p[0]
def test_bool(self): def test_bool(self):
w = lambda x: self.wbuf().write_bool(x).write_flush() # noqa w = lambda x: self.wbuf().write_bool(x).write_flush() # noqa
r = lambda x: self.rbuf(x).read_bool() # noqa r = lambda x: self.rbuf(x).read_bool() # noqa
tc = [(True, '01'), tc = [(True, '01'),
(False, '00')] (False, '00')]
for p in tc: for p in tc:
assert w(p[0]) == self._b(p[1]) assert w(p[0]) == self._b(p[1])
assert r(self._b(p[1])) == p[0] assert r(self._b(p[1])) == p[0]
def test_int(self): def test_int(self):
w = lambda x: self.wbuf().write_int(x).write_flush() # noqa w = lambda x: self.wbuf().write_int(x).write_flush() # noqa
r = lambda x: self.rbuf(x).read_int() # noqa r = lambda x: self.rbuf(x).read_int() # noqa
tc = [(0x00, '00 00 00 00'), tc = [(0x00, '00 00 00 00'),
(0x01, '00 00 00 01'), (0x01, '00 00 00 01'),
(0xabcd, '00 00 ab cd'), (0xabcd, '00 00 ab cd'),
(0xffffffff, 'ff ff ff ff')] (0xffffffff, 'ff ff ff ff')]
for p in tc: for p in tc:
assert w(p[0]) == self._b(p[1]) assert w(p[0]) == self._b(p[1])
assert r(self._b(p[1])) == p[0] assert r(self._b(p[1])) == p[0]
def test_string(self): def test_string(self):
w = lambda x: self.wbuf().write_string(x).write_flush() # noqa w = lambda x: self.wbuf().write_string(x).write_flush() # noqa
r = lambda x: self.rbuf(x).read_string() # noqa r = lambda x: self.rbuf(x).read_string() # noqa
tc = [(u'abc1', '00 00 00 04 61 62 63 31'), tc = [('abc1', '00 00 00 04 61 62 63 31'),
(b'abc2', '00 00 00 04 61 62 63 32')] (b'abc2', '00 00 00 04 61 62 63 32')]
for p in tc: for p in tc:
v = p[0] v = p[0]
assert w(v) == self._b(p[1]) assert w(v) == self._b(p[1])
if not isinstance(v, bytes): if not isinstance(v, bytes):
v = bytes(bytearray(v, 'utf-8')) v = bytes(bytearray(v, 'utf-8'))
assert r(self._b(p[1])) == v assert r(self._b(p[1])) == v
def test_list(self): def test_list(self):
w = lambda x: self.wbuf().write_list(x).write_flush() # noqa w = lambda x: self.wbuf().write_list(x).write_flush() # noqa
r = lambda x: self.rbuf(x).read_list() # noqa r = lambda x: self.rbuf(x).read_list() # noqa
tc = [(['d', 'ef', 'ault'], '00 00 00 09 64 2c 65 66 2c 61 75 6c 74')] tc = [(['d', 'ef', 'ault'], '00 00 00 09 64 2c 65 66 2c 61 75 6c 74')]
for p in tc: for p in tc:
assert w(p[0]) == self._b(p[1]) assert w(p[0]) == self._b(p[1])
assert r(self._b(p[1])) == p[0] assert r(self._b(p[1])) == p[0]
def test_list_nonutf8(self): def test_list_nonutf8(self):
r = lambda x: self.rbuf(x).read_list() # noqa r = lambda x: self.rbuf(x).read_list() # noqa
src = self._b('00 00 00 04 de ad be ef') src = self._b('00 00 00 04 de ad be ef')
dst = [(b'\xde\xad' + self.utf8rchar + self.utf8rchar).decode('utf-8')] dst = [(b'\xde\xad' + self.utf8rchar + self.utf8rchar).decode('utf-8')]
assert r(src) == dst assert r(src) == dst
def test_line(self): def test_line(self):
w = lambda x: self.wbuf().write_line(x).write_flush() # noqa w = lambda x: self.wbuf().write_line(x).write_flush() # noqa
r = lambda x: self.rbuf(x).read_line() # noqa r = lambda x: self.rbuf(x).read_line() # noqa
tc = [(u'example line', '65 78 61 6d 70 6c 65 20 6c 69 6e 65 0d 0a')] tc = [('example line', '65 78 61 6d 70 6c 65 20 6c 69 6e 65 0d 0a')]
for p in tc: for p in tc:
assert w(p[0]) == self._b(p[1]) assert w(p[0]) == self._b(p[1])
assert r(self._b(p[1])) == p[0] assert r(self._b(p[1])) == p[0]
def test_line_nonutf8(self): def test_line_nonutf8(self):
r = lambda x: self.rbuf(x).read_line() # noqa r = lambda x: self.rbuf(x).read_line() # noqa
src = self._b('de ad be af') src = self._b('de ad be af')
dst = (b'\xde\xad' + self.utf8rchar + self.utf8rchar).decode('utf-8') dst = (b'\xde\xad' + self.utf8rchar + self.utf8rchar).decode('utf-8')
assert r(src) == dst assert r(src) == dst
def test_bitlen(self): def test_bitlen(self):
# pylint: disable=protected-access # pylint: disable=protected-access
class Py26Int(int): class Py26Int(int):
def bit_length(self): def bit_length(self):
raise AttributeError raise AttributeError
assert self.wbuf._bitlength(42) == 6 assert self.wbuf._bitlength(42) == 6
assert self.wbuf._bitlength(Py26Int(42)) == 6 assert self.wbuf._bitlength(Py26Int(42)) == 6
def test_mpint1(self): def test_mpint1(self):
mpint1w = lambda x: self.wbuf().write_mpint1(x).write_flush() # noqa mpint1w = lambda x: self.wbuf().write_mpint1(x).write_flush() # noqa
mpint1r = lambda x: self.rbuf(x).read_mpint1() # noqa mpint1r = lambda x: self.rbuf(x).read_mpint1() # noqa
tc = [(0x0, '00 00'), tc = [(0x0, '00 00'),
(0x1234, '00 0d 12 34'), (0x1234, '00 0d 12 34'),
(0x12345, '00 11 01 23 45'), (0x12345, '00 11 01 23 45'),
(0xdeadbeef, '00 20 de ad be ef')] (0xdeadbeef, '00 20 de ad be ef')]
for p in tc: for p in tc:
assert mpint1w(p[0]) == self._b(p[1]) assert mpint1w(p[0]) == self._b(p[1])
assert mpint1r(self._b(p[1])) == p[0] assert mpint1r(self._b(p[1])) == p[0]
def test_mpint2(self): def test_mpint2(self):
mpint2w = lambda x: self.wbuf().write_mpint2(x).write_flush() # noqa mpint2w = lambda x: self.wbuf().write_mpint2(x).write_flush() # noqa
mpint2r = lambda x: self.rbuf(x).read_mpint2() # noqa mpint2r = lambda x: self.rbuf(x).read_mpint2() # noqa
tc = [(0x0, '00 00 00 00'), tc = [(0x0, '00 00 00 00'),
(0x80, '00 00 00 02 00 80'), (0x80, '00 00 00 02 00 80'),
(0x9a378f9b2e332a7, '00 00 00 08 09 a3 78 f9 b2 e3 32 a7'), (0x9a378f9b2e332a7, '00 00 00 08 09 a3 78 f9 b2 e3 32 a7'),
(-0x1234, '00 00 00 02 ed cc'), (-0x1234, '00 00 00 02 ed cc'),
(-0xdeadbeef, '00 00 00 05 ff 21 52 41 11'), (-0xdeadbeef, '00 00 00 05 ff 21 52 41 11'),
(-0x8000, '00 00 00 02 80 00'), (-0x8000, '00 00 00 02 80 00'),
(-0x80, '00 00 00 01 80')] (-0x80, '00 00 00 01 80')]
for p in tc: for p in tc:
assert mpint2w(p[0]) == self._b(p[1]) assert mpint2w(p[0]) == self._b(p[1])
assert mpint2r(self._b(p[1])) == p[0] assert mpint2r(self._b(p[1])) == p[0]
assert mpint2r(self._b('00 00 00 02 ff 80')) == -0x80 assert mpint2r(self._b('00 00 00 02 ff 80')) == -0x80

41
test/test_build_struct.py Normal file
View File

@ -0,0 +1,41 @@
import os
import pytest
@pytest.fixture
def kex(ssh_audit):
kex_algs, key_algs = [], []
enc, mac, compression, languages = [], [], ['none'], []
cli = ssh_audit.SSH2.KexParty(enc, mac, compression, languages)
enc, mac, compression, languages = [], [], ['none'], []
srv = ssh_audit.SSH2.KexParty(enc, mac, compression, languages)
cookie = os.urandom(16)
kex = ssh_audit.SSH2.Kex(cookie, kex_algs, key_algs, cli, srv, 0)
return kex
def test_prevent_runtime_error_regression(ssh_audit, kex):
"""Prevent a regression of https://github.com/jtesta/ssh-audit/issues/41
The following test setup does not contain any sensible data.
It was made up to reproduce a situation when there are several host
keys, and an error occurred when iterating and modifying them at the
same time.
"""
kex.set_host_key("ssh-rsa", b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00")
kex.set_host_key("ssh-rsa1", b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00")
kex.set_host_key("ssh-rsa2", b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00")
kex.set_host_key("ssh-rsa3", b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00")
kex.set_host_key("ssh-rsa4", b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00")
kex.set_host_key("ssh-rsa5", b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00")
kex.set_host_key("ssh-rsa6", b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00")
kex.set_host_key("ssh-rsa7", b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00")
kex.set_host_key("ssh-rsa8", b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00")
rv = ssh_audit.build_struct(banner=None, kex=kex)
assert len(rv["fingerprints"]) == 9
for key in ['banner', 'compression', 'enc', 'fingerprints', 'kex', 'key', 'mac']:
assert key in rv

View File

@ -1,162 +1,162 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket import socket
import errno import errno
import pytest import pytest
# pylint: disable=attribute-defined-outside-init # pylint: disable=attribute-defined-outside-init
class TestErrors(object): class TestErrors:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def init(self, ssh_audit): def init(self, ssh_audit):
self.AuditConf = ssh_audit.AuditConf self.AuditConf = ssh_audit.AuditConf
self.audit = ssh_audit.audit self.audit = ssh_audit.audit
def _conf(self): def _conf(self):
conf = self.AuditConf('localhost', 22) conf = self.AuditConf('localhost', 22)
conf.colors = False conf.colors = False
conf.batch = True conf.batch = True
return conf return conf
def _audit(self, spy, conf=None, sysexit=True): def _audit(self, spy, conf=None, exit_expected=False):
if conf is None: if conf is None:
conf = self._conf() conf = self._conf()
spy.begin() spy.begin()
if sysexit:
with pytest.raises(SystemExit):
self.audit(conf)
else:
self.audit(conf)
lines = spy.flush()
return lines
def test_connection_unresolved(self, output_spy, virtual_socket): if exit_expected:
vsocket = virtual_socket with pytest.raises(SystemExit):
vsocket.gsock.addrinfodata['localhost#22'] = [] self.audit(conf)
lines = self._audit(output_spy) else:
assert len(lines) == 1 ret = self.audit(conf)
assert 'has no DNS records' in lines[-1] assert ret != 0
def test_connection_refused(self, output_spy, virtual_socket): lines = spy.flush()
vsocket = virtual_socket return lines
vsocket.errors['connect'] = socket.error(errno.ECONNREFUSED, 'Connection refused')
lines = self._audit(output_spy)
assert len(lines) == 1
assert 'Connection refused' in lines[-1]
def test_connection_timeout(self, output_spy, virtual_socket): def test_connection_unresolved(self, output_spy, virtual_socket):
vsocket = virtual_socket vsocket = virtual_socket
vsocket.errors['connect'] = socket.timeout('timed out') vsocket.gsock.addrinfodata['localhost#22'] = []
lines = self._audit(output_spy) lines = self._audit(output_spy, exit_expected=True)
assert len(lines) == 1 assert len(lines) == 1
assert 'timed out' in lines[-1] assert 'has no DNS records' in lines[-1]
def test_recv_empty(self, output_spy, virtual_socket): def test_connection_refused(self, output_spy, virtual_socket):
vsocket = virtual_socket vsocket = virtual_socket
lines = self._audit(output_spy) vsocket.errors['connect'] = socket.error(errno.ECONNREFUSED, 'Connection refused')
assert len(lines) == 1 lines = self._audit(output_spy, exit_expected=True)
assert 'did not receive banner' in lines[-1] assert len(lines) == 1
assert 'Connection refused' in lines[-1]
def test_recv_timeout(self, output_spy, virtual_socket): def test_connection_timeout(self, output_spy, virtual_socket):
vsocket = virtual_socket vsocket = virtual_socket
vsocket.rdata.append(socket.timeout('timed out')) vsocket.errors['connect'] = socket.timeout('timed out')
lines = self._audit(output_spy) lines = self._audit(output_spy, exit_expected=True)
assert len(lines) == 1 assert len(lines) == 1
assert 'did not receive banner' in lines[-1] assert 'timed out' in lines[-1]
assert 'timed out' in lines[-1]
def test_recv_retry_till_timeout(self, output_spy, virtual_socket): def test_recv_empty(self, output_spy, virtual_socket):
vsocket = virtual_socket lines = self._audit(output_spy)
vsocket.rdata.append(socket.error(errno.EAGAIN, 'Resource temporarily unavailable')) assert len(lines) == 1
vsocket.rdata.append(socket.error(errno.EWOULDBLOCK, 'Resource temporarily unavailable')) assert 'did not receive banner' in lines[-1]
vsocket.rdata.append(socket.error(errno.EAGAIN, 'Resource temporarily unavailable'))
vsocket.rdata.append(socket.timeout('timed out'))
lines = self._audit(output_spy)
assert len(lines) == 1
assert 'did not receive banner' in lines[-1]
assert 'timed out' in lines[-1]
def test_recv_retry_till_reset(self, output_spy, virtual_socket): def test_recv_timeout(self, output_spy, virtual_socket):
vsocket = virtual_socket vsocket = virtual_socket
vsocket.rdata.append(socket.error(errno.EAGAIN, 'Resource temporarily unavailable')) vsocket.rdata.append(socket.timeout('timed out'))
vsocket.rdata.append(socket.error(errno.EWOULDBLOCK, 'Resource temporarily unavailable')) lines = self._audit(output_spy)
vsocket.rdata.append(socket.error(errno.EAGAIN, 'Resource temporarily unavailable')) assert len(lines) == 1
vsocket.rdata.append(socket.error(errno.ECONNRESET, 'Connection reset by peer')) assert 'did not receive banner' in lines[-1]
lines = self._audit(output_spy) assert 'timed out' in lines[-1]
assert len(lines) == 1
assert 'did not receive banner' in lines[-1]
assert 'reset by peer' in lines[-1]
def test_connection_closed_before_banner(self, output_spy, virtual_socket): def test_recv_retry_till_timeout(self, output_spy, virtual_socket):
vsocket = virtual_socket vsocket = virtual_socket
vsocket.rdata.append(socket.error(errno.ECONNRESET, 'Connection reset by peer')) vsocket.rdata.append(socket.error(errno.EAGAIN, 'Resource temporarily unavailable'))
lines = self._audit(output_spy) vsocket.rdata.append(socket.error(errno.EWOULDBLOCK, 'Resource temporarily unavailable'))
assert len(lines) == 1 vsocket.rdata.append(socket.error(errno.EAGAIN, 'Resource temporarily unavailable'))
assert 'did not receive banner' in lines[-1] vsocket.rdata.append(socket.timeout('timed out'))
assert 'reset by peer' in lines[-1] lines = self._audit(output_spy)
assert len(lines) == 1
assert 'did not receive banner' in lines[-1]
assert 'timed out' in lines[-1]
def test_connection_closed_after_header(self, output_spy, virtual_socket): def test_recv_retry_till_reset(self, output_spy, virtual_socket):
vsocket = virtual_socket vsocket = virtual_socket
vsocket.rdata.append(b'header line 1\n') vsocket.rdata.append(socket.error(errno.EAGAIN, 'Resource temporarily unavailable'))
vsocket.rdata.append(b'\n') vsocket.rdata.append(socket.error(errno.EWOULDBLOCK, 'Resource temporarily unavailable'))
vsocket.rdata.append(b'header line 2\n') vsocket.rdata.append(socket.error(errno.EAGAIN, 'Resource temporarily unavailable'))
vsocket.rdata.append(socket.error(errno.ECONNRESET, 'Connection reset by peer')) vsocket.rdata.append(socket.error(errno.ECONNRESET, 'Connection reset by peer'))
lines = self._audit(output_spy) lines = self._audit(output_spy)
assert len(lines) == 3 assert len(lines) == 1
assert 'did not receive banner' in lines[-1] assert 'did not receive banner' in lines[-1]
assert 'reset by peer' in lines[-1] assert 'reset by peer' in lines[-1]
def test_connection_closed_after_banner(self, output_spy, virtual_socket): def test_connection_closed_before_banner(self, output_spy, virtual_socket):
vsocket = virtual_socket vsocket = virtual_socket
vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\r\n') vsocket.rdata.append(socket.error(errno.ECONNRESET, 'Connection reset by peer'))
vsocket.rdata.append(socket.error(54, 'Connection reset by peer')) lines = self._audit(output_spy)
lines = self._audit(output_spy) assert len(lines) == 1
assert len(lines) == 2 assert 'did not receive banner' in lines[-1]
assert 'error reading packet' in lines[-1] assert 'reset by peer' in lines[-1]
assert 'reset by peer' in lines[-1]
def test_empty_data_after_banner(self, output_spy, virtual_socket): def test_connection_closed_after_header(self, output_spy, virtual_socket):
vsocket = virtual_socket vsocket = virtual_socket
vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\r\n') vsocket.rdata.append(b'header line 1\n')
lines = self._audit(output_spy) vsocket.rdata.append(b'\n')
assert len(lines) == 2 vsocket.rdata.append(b'header line 2\n')
assert 'error reading packet' in lines[-1] vsocket.rdata.append(socket.error(errno.ECONNRESET, 'Connection reset by peer'))
assert 'empty' in lines[-1] lines = self._audit(output_spy)
assert len(lines) == 3
assert 'did not receive banner' in lines[-1]
assert 'reset by peer' in lines[-1]
def test_wrong_data_after_banner(self, output_spy, virtual_socket): def test_connection_closed_after_banner(self, output_spy, virtual_socket):
vsocket = virtual_socket vsocket = virtual_socket
vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\r\n') vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\r\n')
vsocket.rdata.append(b'xxx\n') vsocket.rdata.append(socket.error(54, 'Connection reset by peer'))
lines = self._audit(output_spy) lines = self._audit(output_spy)
assert len(lines) == 2 assert len(lines) == 2
assert 'error reading packet' in lines[-1] assert 'error reading packet' in lines[-1]
assert 'xxx' in lines[-1] assert 'reset by peer' in lines[-1]
def test_non_ascii_banner(self, output_spy, virtual_socket): def test_empty_data_after_banner(self, output_spy, virtual_socket):
vsocket = virtual_socket vsocket = virtual_socket
vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\xc3\xbc\r\n') vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\r\n')
lines = self._audit(output_spy) lines = self._audit(output_spy)
assert len(lines) == 3 assert len(lines) == 2
assert 'error reading packet' in lines[-1] assert 'error reading packet' in lines[-1]
assert 'ASCII' in lines[-2] assert 'empty' in lines[-1]
assert lines[-3].endswith('SSH-2.0-ssh-audit-test?')
def test_nonutf8_data_after_banner(self, output_spy, virtual_socket): def test_wrong_data_after_banner(self, output_spy, virtual_socket):
vsocket = virtual_socket vsocket = virtual_socket
vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\r\n') vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\r\n')
vsocket.rdata.append(b'\x81\xff\n') vsocket.rdata.append(b'xxx\n')
lines = self._audit(output_spy) lines = self._audit(output_spy)
assert len(lines) == 2 assert len(lines) == 2
assert 'error reading packet' in lines[-1] assert 'error reading packet' in lines[-1]
assert '\\x81\\xff' in lines[-1] assert 'xxx' in lines[-1]
def test_protocol_mismatch_by_conf(self, output_spy, virtual_socket): def test_non_ascii_banner(self, output_spy, virtual_socket):
vsocket = virtual_socket vsocket = virtual_socket
vsocket.rdata.append(b'SSH-1.3-ssh-audit-test\r\n') vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\xc3\xbc\r\n')
vsocket.rdata.append(b'Protocol major versions differ.\n') lines = self._audit(output_spy)
conf = self._conf() assert len(lines) == 3
conf.ssh1, conf.ssh2 = True, False assert 'error reading packet' in lines[-1]
lines = self._audit(output_spy, conf) assert 'ASCII' in lines[-2]
assert len(lines) == 3 assert lines[-3].endswith('SSH-2.0-ssh-audit-test?')
assert 'error reading packet' in lines[-1]
assert 'major versions differ' in lines[-1] def test_nonutf8_data_after_banner(self, output_spy, virtual_socket):
vsocket = virtual_socket
vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\r\n')
vsocket.rdata.append(b'\x81\xff\n')
lines = self._audit(output_spy)
assert len(lines) == 2
assert 'error reading packet' in lines[-1]
assert '\\x81\\xff' in lines[-1]
def test_protocol_mismatch_by_conf(self, output_spy, virtual_socket):
vsocket = virtual_socket
vsocket.rdata.append(b'SSH-1.3-ssh-audit-test\r\n')
vsocket.rdata.append(b'Protocol major versions differ.\n')
conf = self._conf()
conf.ssh1, conf.ssh2 = True, False
lines = self._audit(output_spy, conf)
assert len(lines) == 3
assert 'error reading packet' in lines[-1]
assert 'major versions differ' in lines[-1]

View File

@ -1,175 +1,172 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
import pytest import pytest
# pylint: disable=attribute-defined-outside-init # pylint: disable=attribute-defined-outside-init
class TestOutput(object): class TestOutput:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def init(self, ssh_audit): def init(self, ssh_audit):
self.Output = ssh_audit.Output self.Output = ssh_audit.Output
self.OutputBuffer = ssh_audit.OutputBuffer self.OutputBuffer = ssh_audit.OutputBuffer
def test_output_buffer_no_lines(self, output_spy): def test_output_buffer_no_lines(self, output_spy):
output_spy.begin() output_spy.begin()
with self.OutputBuffer() as obuf: with self.OutputBuffer() as obuf:
pass pass
assert output_spy.flush() == [] assert output_spy.flush() == []
output_spy.begin() output_spy.begin()
with self.OutputBuffer() as obuf: with self.OutputBuffer() as obuf:
pass pass
obuf.flush() obuf.flush()
assert output_spy.flush() == [] assert output_spy.flush() == []
def test_output_buffer_no_flush(self, output_spy): def test_output_buffer_no_flush(self, output_spy):
output_spy.begin() output_spy.begin()
with self.OutputBuffer(): with self.OutputBuffer():
print(u'abc') print('abc')
assert output_spy.flush() == [] assert output_spy.flush() == []
def test_output_buffer_flush(self, output_spy): def test_output_buffer_flush(self, output_spy):
output_spy.begin() output_spy.begin()
with self.OutputBuffer() as obuf: with self.OutputBuffer() as obuf:
print(u'abc') print('abc')
print() print()
print(u'def') print('def')
obuf.flush() obuf.flush()
assert output_spy.flush() == [u'abc', u'', u'def'] assert output_spy.flush() == ['abc', '', 'def']
def test_output_defaults(self): def test_output_defaults(self):
out = self.Output() out = self.Output()
# default: on # default: on
assert out.batch is False assert out.batch is False
assert out.use_colors is True assert out.use_colors is True
assert out.level == 'info' assert out.level == 'info'
def test_output_colors(self, output_spy): def test_output_colors(self, output_spy):
out = self.Output() out = self.Output()
# test without colors # test without colors
out.use_colors = False out.use_colors = False
output_spy.begin() output_spy.begin()
out.info('info color') out.info('info color')
assert output_spy.flush() == [u'info color'] assert output_spy.flush() == ['info color']
output_spy.begin() output_spy.begin()
out.head('head color') out.head('head color')
assert output_spy.flush() == [u'head color'] assert output_spy.flush() == ['head color']
output_spy.begin() output_spy.begin()
out.good('good color') out.good('good color')
assert output_spy.flush() == [u'good color'] assert output_spy.flush() == ['good color']
output_spy.begin() output_spy.begin()
out.warn('warn color') out.warn('warn color')
assert output_spy.flush() == [u'warn color'] assert output_spy.flush() == ['warn color']
output_spy.begin() output_spy.begin()
out.fail('fail color') out.fail('fail color')
assert output_spy.flush() == [u'fail color'] assert output_spy.flush() == ['fail color']
if not out.colors_supported: if not out.colors_supported:
return return
# test with colors # test with colors
out.use_colors = True out.use_colors = True
output_spy.begin() output_spy.begin()
out.info('info color') out.info('info color')
assert output_spy.flush() == [u'info color'] assert output_spy.flush() == ['info color']
output_spy.begin() output_spy.begin()
out.head('head color') out.head('head color')
assert output_spy.flush() == [u'\x1b[0;36mhead color\x1b[0m'] assert output_spy.flush() == ['\x1b[0;36mhead color\x1b[0m']
output_spy.begin() output_spy.begin()
out.good('good color') out.good('good color')
assert output_spy.flush() == [u'\x1b[0;32mgood color\x1b[0m'] assert output_spy.flush() == ['\x1b[0;32mgood color\x1b[0m']
output_spy.begin() output_spy.begin()
out.warn('warn color') out.warn('warn color')
assert output_spy.flush() == [u'\x1b[0;33mwarn color\x1b[0m'] assert output_spy.flush() == ['\x1b[0;33mwarn color\x1b[0m']
output_spy.begin() output_spy.begin()
out.fail('fail color') out.fail('fail color')
assert output_spy.flush() == [u'\x1b[0;31mfail color\x1b[0m'] assert output_spy.flush() == ['\x1b[0;31mfail color\x1b[0m']
def test_output_sep(self, output_spy): def test_output_sep(self, output_spy):
out = self.Output() out = self.Output()
output_spy.begin() output_spy.begin()
out.sep() out.sep()
out.sep() out.sep()
out.sep() out.sep()
assert output_spy.flush() == [u'', u'', u''] assert output_spy.flush() == ['', '', '']
def test_output_levels(self): def test_output_levels(self):
out = self.Output() out = self.Output()
assert out.get_level('info') == 0 assert out.get_level('info') == 0
assert out.get_level('good') == 0 assert out.get_level('good') == 0
assert out.get_level('warn') == 1 assert out.get_level('warn') == 1
assert out.get_level('fail') == 2 assert out.get_level('fail') == 2
assert out.get_level('unknown') > 2 assert out.get_level('unknown') > 2
def test_output_level_property(self): def test_output_level_property(self):
out = self.Output() out = self.Output()
out.level = 'info' out.level = 'info'
assert out.level == 'info' assert out.level == 'info'
out.level = 'good' out.level = 'good'
assert out.level == 'info' assert out.level == 'info'
out.level = 'warn' out.level = 'warn'
assert out.level == 'warn' assert out.level == 'warn'
out.level = 'fail' out.level = 'fail'
assert out.level == 'fail' assert out.level == 'fail'
out.level = 'invalid level' out.level = 'invalid level'
assert out.level == 'unknown' assert out.level == 'unknown'
def test_output_level(self, output_spy): def test_output_level(self, output_spy):
out = self.Output() out = self.Output()
# visible: all # visible: all
out.level = 'info' out.level = 'info'
output_spy.begin() output_spy.begin()
out.info('info color') out.info('info color')
out.head('head color') out.head('head color')
out.good('good color') out.good('good color')
out.warn('warn color') out.warn('warn color')
out.fail('fail color') out.fail('fail color')
assert len(output_spy.flush()) == 5 assert len(output_spy.flush()) == 5
# visible: head, warn, fail # visible: head, warn, fail
out.level = 'warn' out.level = 'warn'
output_spy.begin() output_spy.begin()
out.info('info color') out.info('info color')
out.head('head color') out.head('head color')
out.good('good color') out.good('good color')
out.warn('warn color') out.warn('warn color')
out.fail('fail color') out.fail('fail color')
assert len(output_spy.flush()) == 3 assert len(output_spy.flush()) == 3
# visible: head, fail # visible: head, fail
out.level = 'fail' out.level = 'fail'
output_spy.begin() output_spy.begin()
out.info('info color') out.info('info color')
out.head('head color') out.head('head color')
out.good('good color') out.good('good color')
out.warn('warn color') out.warn('warn color')
out.fail('fail color') out.fail('fail color')
assert len(output_spy.flush()) == 2 assert len(output_spy.flush()) == 2
# visible: head # visible: head
out.level = 'invalid level' out.level = 'invalid level'
output_spy.begin() output_spy.begin()
out.info('info color') out.info('info color')
out.head('head color') out.head('head color')
out.good('good color') out.good('good color')
out.warn('warn color') out.warn('warn color')
out.fail('fail color') out.fail('fail color')
assert len(output_spy.flush()) == 1 assert len(output_spy.flush()) == 1
def test_output_batch(self, output_spy): def test_output_batch(self, output_spy):
out = self.Output() out = self.Output()
# visible: all # visible: all
output_spy.begin() output_spy.begin()
out.level = 'info' out.level = 'info'
out.batch = False out.batch = False
out.info('info color') out.info('info color')
out.head('head color') out.head('head color')
out.good('good color') out.good('good color')
out.warn('warn color') out.warn('warn color')
out.fail('fail color') out.fail('fail color')
assert len(output_spy.flush()) == 5 assert len(output_spy.flush()) == 5
# visible: all except head # visible: all except head
output_spy.begin() output_spy.begin()
out.level = 'info' out.level = 'info'
out.batch = True out.batch = True
out.info('info color') out.info('info color')
out.head('head color') out.head('head color')
out.good('good color') out.good('good color')
out.warn('warn color') out.warn('warn color')
out.fail('fail color') out.fail('fail color')
assert len(output_spy.flush()) == 4 assert len(output_spy.flush()) == 4

337
test/test_policy.py Normal file
View File

@ -0,0 +1,337 @@
import hashlib
import pytest
from datetime import date
class TestPolicy:
@pytest.fixture(autouse=True)
def init(self, ssh_audit):
self.Policy = ssh_audit.Policy
self.wbuf = ssh_audit.WriteBuf
self.ssh2 = ssh_audit.SSH2
def _get_kex(self):
'''Returns an SSH2.Kex object to simulate a server connection.'''
w = self.wbuf()
w.write(b'\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff')
w.write_list(['kex_alg1', 'kex_alg2'])
w.write_list(['key_alg1', 'key_alg2'])
w.write_list(['cipher_alg1', 'cipher_alg2', 'cipher_alg3'])
w.write_list(['cipher_alg1', 'cipher_alg2', 'cipher_alg3'])
w.write_list(['mac_alg1', 'mac_alg2', 'mac_alg3'])
w.write_list(['mac_alg1', 'mac_alg2', 'mac_alg3'])
w.write_list(['comp_alg1', 'comp_alg2'])
w.write_list(['comp_alg1', 'comp_alg2'])
w.write_list([''])
w.write_list([''])
w.write_byte(False)
w.write_int(0)
return self.ssh2.Kex.parse(w.write_flush())
def test_policy_basic(self):
'''Ensure that a basic policy can be parsed correctly.'''
policy_data = '''# This is a comment
name = "Test Policy"
version = 1
compressions = comp_alg1
host keys = key_alg1
key exchanges = kex_alg1, kex_alg2
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
macs = mac_alg1, mac_alg2, mac_alg3'''
policy = self.Policy(policy_data=policy_data)
assert str(policy) == "Name: [Test Policy]\nVersion: [1]\nBanner: {undefined}\nCompressions: comp_alg1\nHost Keys: key_alg1\nKey Exchanges: kex_alg1, kex_alg2\nCiphers: cipher_alg1, cipher_alg2, cipher_alg3\nMACs: mac_alg1, mac_alg2, mac_alg3"
def test_policy_invalid_1(self):
'''Basic policy, but with 'ciphersx' instead of 'ciphers'.'''
policy_data = '''# This is a comment
name = "Test Policy"
version = 1
compressions = comp_alg1
host keys = key_alg1
key exchanges = kex_alg1, kex_alg2
ciphersx = cipher_alg1, cipher_alg2, cipher_alg3
macs = mac_alg1, mac_alg2, mac_alg3'''
failed = False
try:
self.Policy(policy_data=policy_data)
except ValueError:
failed = True
assert failed, "Invalid policy did not cause Policy object to throw exception"
def test_policy_invalid_2(self):
'''Basic policy, but is missing the required name field.'''
policy_data = '''# This is a comment
#name = "Test Policy"
version = 1
compressions = comp_alg1
host keys = key_alg1
key exchanges = kex_alg1, kex_alg2
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
macs = mac_alg1, mac_alg2, mac_alg3'''
failed = False
try:
self.Policy(policy_data=policy_data)
except ValueError:
failed = True
assert failed, "Invalid policy did not cause Policy object to throw exception"
def test_policy_invalid_3(self):
'''Basic policy, but is missing the required version field.'''
policy_data = '''# This is a comment
name = "Test Policy"
#version = 1
compressions = comp_alg1
host keys = key_alg1
key exchanges = kex_alg1, kex_alg2
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
macs = mac_alg1, mac_alg2, mac_alg3'''
failed = False
try:
self.Policy(policy_data=policy_data)
except ValueError:
failed = True
assert failed, "Invalid policy did not cause Policy object to throw exception"
def test_policy_invalid_4(self):
'''Basic policy, but is missing quotes in the name field.'''
policy_data = '''# This is a comment
name = Test Policy
version = 1
compressions = comp_alg1
host keys = key_alg1
key exchanges = kex_alg1, kex_alg2
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
macs = mac_alg1, mac_alg2, mac_alg3'''
failed = False
try:
self.Policy(policy_data=policy_data)
except ValueError:
failed = True
assert failed, "Invalid policy did not cause Policy object to throw exception"
def test_policy_invalid_5(self):
'''Basic policy, but is missing quotes in the banner field.'''
policy_data = '''# This is a comment
name = "Test Policy"
version = 1
banner = 0mg
compressions = comp_alg1
host keys = key_alg1
key exchanges = kex_alg1, kex_alg2
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
macs = mac_alg1, mac_alg2, mac_alg3'''
failed = False
try:
self.Policy(policy_data=policy_data)
except ValueError:
failed = True
assert failed, "Invalid policy did not cause Policy object to throw exception"
def test_policy_invalid_6(self):
'''Basic policy, but is missing quotes in the header field.'''
policy_data = '''# This is a comment
name = "Test Policy"
version = 1
header = 0mg
compressions = comp_alg1
host keys = key_alg1
key exchanges = kex_alg1, kex_alg2
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
macs = mac_alg1, mac_alg2, mac_alg3'''
failed = False
try:
self.Policy(policy_data=policy_data)
except ValueError:
failed = True
assert failed, "Invalid policy did not cause Policy object to throw exception"
def test_policy_create_1(self):
'''Creates a policy from a kex and ensures it is generated exactly as expected.'''
kex = self._get_kex()
pol_data = self.Policy.create('www.l0l.com', 'bannerX', kex, False)
# Today's date is embedded in the policy, so filter it out to get repeatable results.
pol_data = pol_data.replace(date.today().strftime('%Y/%m/%d'), '[todays date]')
# Instead of writing out the entire expected policy--line by line--just check that it has the expected hash.
assert hashlib.sha256(pol_data.encode('ascii')).hexdigest() == '4af7777fb57a1dad0cf438c899a11d4f625fd9276ea3bb5ef5c9fe8806cb47dc'
def test_policy_evaluate_passing_1(self):
'''Creates a policy and evaluates it against the same server'''
kex = self._get_kex()
policy_data = self.Policy.create('www.l0l.com', None, kex, False)
policy = self.Policy(policy_data=policy_data)
ret, errors, error_str = policy.evaluate('SSH Server 1.0', kex)
assert ret is True
assert len(errors) == 0
print(error_str)
assert len(error_str) == 0
def test_policy_evaluate_failing_1(self):
'''Ensure that a policy with a specified banner fails against a server with a different banner'''
policy_data = '''name = "Test Policy"
version = 1
banner = "XXX mismatched banner XXX"
compressions = comp_alg1, comp_alg2
host keys = key_alg1, key_alg2
key exchanges = kex_alg1, kex_alg2
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
macs = mac_alg1, mac_alg2, mac_alg3'''
policy = self.Policy(policy_data=policy_data)
ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex())
assert ret is False
assert len(errors) == 1
assert error_str.find('Banner did not match.') != -1
def test_policy_evaluate_failing_2(self):
'''Ensure that a mismatched compressions list results in a failure'''
policy_data = '''name = "Test Policy"
version = 1
compressions = XXXmismatchedXXX, comp_alg1, comp_alg2
host keys = key_alg1, key_alg2
key exchanges = kex_alg1, kex_alg2
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
macs = mac_alg1, mac_alg2, mac_alg3'''
policy = self.Policy(policy_data=policy_data)
ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex())
assert ret is False
assert len(errors) == 1
assert error_str.find('Compression did not match.') != -1
def test_policy_evaluate_failing_3(self):
'''Ensure that a mismatched host keys results in a failure'''
policy_data = '''name = "Test Policy"
version = 1
compressions = comp_alg1, comp_alg2
host keys = XXXmismatchedXXX, key_alg1, key_alg2
key exchanges = kex_alg1, kex_alg2
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
macs = mac_alg1, mac_alg2, mac_alg3'''
policy = self.Policy(policy_data=policy_data)
ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex())
assert ret is False
assert len(errors) == 1
assert error_str.find('Host keys did not match.') != -1
def test_policy_evaluate_failing_4(self):
'''Ensure that a mismatched key exchange list results in a failure'''
policy_data = '''name = "Test Policy"
version = 1
compressions = comp_alg1, comp_alg2
host keys = key_alg1, key_alg2
key exchanges = XXXmismatchedXXX, kex_alg1, kex_alg2
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
macs = mac_alg1, mac_alg2, mac_alg3'''
policy = self.Policy(policy_data=policy_data)
ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex())
assert ret is False
assert len(errors) == 1
assert error_str.find('Key exchanges did not match.') != -1
def test_policy_evaluate_failing_5(self):
'''Ensure that a mismatched cipher list results in a failure'''
policy_data = '''name = "Test Policy"
version = 1
compressions = comp_alg1, comp_alg2
host keys = key_alg1, key_alg2
key exchanges = kex_alg1, kex_alg2
ciphers = cipher_alg1, XXXmismatched, cipher_alg2, cipher_alg3
macs = mac_alg1, mac_alg2, mac_alg3'''
policy = self.Policy(policy_data=policy_data)
ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex())
assert ret is False
assert len(errors) == 1
assert error_str.find('Ciphers did not match.') != -1
def test_policy_evaluate_failing_6(self):
'''Ensure that a mismatched MAC list results in a failure'''
policy_data = '''name = "Test Policy"
version = 1
compressions = comp_alg1, comp_alg2
host keys = key_alg1, key_alg2
key exchanges = kex_alg1, kex_alg2
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
macs = mac_alg1, mac_alg2, XXXmismatched, mac_alg3'''
policy = self.Policy(policy_data=policy_data)
ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex())
assert ret is False
assert len(errors) == 1
assert error_str.find('MACs did not match.') != -1
def test_policy_evaluate_failing_7(self):
'''Ensure that a mismatched host keys and MACs results in a failure'''
policy_data = '''name = "Test Policy"
version = 1
compressions = comp_alg1, comp_alg2
host keys = key_alg1, key_alg2, XXXmismatchedXXX
key exchanges = kex_alg1, kex_alg2
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
macs = mac_alg1, mac_alg2, XXXmismatchedXXX, mac_alg3'''
policy = self.Policy(policy_data=policy_data)
ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex())
assert ret is False
assert len(errors) == 2
assert error_str.find('Host keys did not match.') != -1
assert error_str.find('MACs did not match.') != -1

View File

@ -1,85 +1,79 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket import socket
import pytest import pytest
# pylint: disable=attribute-defined-outside-init,protected-access # pylint: disable=attribute-defined-outside-init,protected-access
class TestResolve(object): class TestResolve:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def init(self, ssh_audit): def init(self, ssh_audit):
self.AuditConf = ssh_audit.AuditConf self.AuditConf = ssh_audit.AuditConf
self.audit = ssh_audit.audit self.audit = ssh_audit.audit
self.ssh = ssh_audit.SSH self.ssh = ssh_audit.SSH
def _conf(self): def _conf(self):
conf = self.AuditConf('localhost', 22) conf = self.AuditConf('localhost', 22)
conf.colors = False conf.colors = False
conf.batch = True conf.batch = True
return conf return conf
def test_resolve_error(self, output_spy, virtual_socket): def test_resolve_error(self, output_spy, virtual_socket):
vsocket = virtual_socket vsocket = virtual_socket
vsocket.gsock.addrinfodata['localhost#22'] = socket.gaierror(8, 'hostname nor servname provided, or not known') vsocket.gsock.addrinfodata['localhost#22'] = socket.gaierror(8, 'hostname nor servname provided, or not known')
s = self.ssh.Socket('localhost', 22) s = self.ssh.Socket('localhost', 22)
conf = self._conf() conf = self._conf()
output_spy.begin() output_spy.begin()
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
r = list(s._resolve(conf.ipvo)) list(s._resolve(conf.ipvo))
lines = output_spy.flush() lines = output_spy.flush()
assert len(lines) == 1 assert len(lines) == 1
assert 'hostname nor servname provided' in lines[-1] assert 'hostname nor servname provided' in lines[-1]
def test_resolve_hostname_without_records(self, output_spy, virtual_socket): def test_resolve_hostname_without_records(self, output_spy, virtual_socket):
vsocket = virtual_socket vsocket = virtual_socket
vsocket.gsock.addrinfodata['localhost#22'] = [] vsocket.gsock.addrinfodata['localhost#22'] = []
s = self.ssh.Socket('localhost', 22) s = self.ssh.Socket('localhost', 22)
conf = self._conf() conf = self._conf()
output_spy.begin() output_spy.begin()
r = list(s._resolve(conf.ipvo)) r = list(s._resolve(conf.ipvo))
assert len(r) == 0 assert len(r) == 0
def test_resolve_ipv4(self, virtual_socket): def test_resolve_ipv4(self, virtual_socket):
vsocket = virtual_socket conf = self._conf()
conf = self._conf() conf.ipv4 = True
conf.ipv4 = True s = self.ssh.Socket('localhost', 22)
s = self.ssh.Socket('localhost', 22) r = list(s._resolve(conf.ipvo))
r = list(s._resolve(conf.ipvo)) assert len(r) == 1
assert len(r) == 1 assert r[0] == (socket.AF_INET, ('127.0.0.1', 22))
assert r[0] == (socket.AF_INET, ('127.0.0.1', 22))
def test_resolve_ipv6(self, virtual_socket): def test_resolve_ipv6(self, virtual_socket):
vsocket = virtual_socket s = self.ssh.Socket('localhost', 22)
s = self.ssh.Socket('localhost', 22) conf = self._conf()
conf = self._conf() conf.ipv6 = True
conf.ipv6 = True r = list(s._resolve(conf.ipvo))
r = list(s._resolve(conf.ipvo)) assert len(r) == 1
assert len(r) == 1 assert r[0] == (socket.AF_INET6, ('::1', 22))
assert r[0] == (socket.AF_INET6, ('::1', 22))
def test_resolve_ipv46_both(self, virtual_socket): def test_resolve_ipv46_both(self, virtual_socket):
vsocket = virtual_socket s = self.ssh.Socket('localhost', 22)
s = self.ssh.Socket('localhost', 22) conf = self._conf()
conf = self._conf() r = list(s._resolve(conf.ipvo))
r = list(s._resolve(conf.ipvo)) assert len(r) == 2
assert len(r) == 2 assert r[0] == (socket.AF_INET, ('127.0.0.1', 22))
assert r[0] == (socket.AF_INET, ('127.0.0.1', 22)) assert r[1] == (socket.AF_INET6, ('::1', 22))
assert r[1] == (socket.AF_INET6, ('::1', 22))
def test_resolve_ipv46_order(self, virtual_socket): def test_resolve_ipv46_order(self, virtual_socket):
vsocket = virtual_socket s = self.ssh.Socket('localhost', 22)
s = self.ssh.Socket('localhost', 22) conf = self._conf()
conf = self._conf() conf.ipv4 = True
conf.ipv4 = True conf.ipv6 = True
conf.ipv6 = True r = list(s._resolve(conf.ipvo))
r = list(s._resolve(conf.ipvo)) assert len(r) == 2
assert len(r) == 2 assert r[0] == (socket.AF_INET, ('127.0.0.1', 22))
assert r[0] == (socket.AF_INET, ('127.0.0.1', 22)) assert r[1] == (socket.AF_INET6, ('::1', 22))
assert r[1] == (socket.AF_INET6, ('::1', 22)) conf = self._conf()
conf = self._conf() conf.ipv6 = True
conf.ipv6 = True conf.ipv4 = True
conf.ipv4 = True r = list(s._resolve(conf.ipvo))
r = list(s._resolve(conf.ipvo)) assert len(r) == 2
assert len(r) == 2 assert r[0] == (socket.AF_INET6, ('::1', 22))
assert r[0] == (socket.AF_INET6, ('::1', 22)) assert r[1] == (socket.AF_INET, ('127.0.0.1', 22))
assert r[1] == (socket.AF_INET, ('127.0.0.1', 22))

View File

@ -1,41 +1,38 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import pytest import pytest
# pylint: disable=attribute-defined-outside-init # pylint: disable=attribute-defined-outside-init
class TestSocket(object): class TestSocket:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def init(self, ssh_audit): def init(self, ssh_audit):
self.ssh = ssh_audit.SSH self.ssh = ssh_audit.SSH
def test_invalid_host(self, virtual_socket): def test_invalid_host(self, virtual_socket):
with pytest.raises(ValueError): with pytest.raises(ValueError):
s = self.ssh.Socket(None, 22) self.ssh.Socket(None, 22)
def test_invalid_port(self, virtual_socket): def test_invalid_port(self, virtual_socket):
with pytest.raises(ValueError): with pytest.raises(ValueError):
s = self.ssh.Socket('localhost', 'abc') self.ssh.Socket('localhost', 'abc')
with pytest.raises(ValueError): with pytest.raises(ValueError):
s = self.ssh.Socket('localhost', -1) self.ssh.Socket('localhost', -1)
with pytest.raises(ValueError): with pytest.raises(ValueError):
s = self.ssh.Socket('localhost', 0) self.ssh.Socket('localhost', 0)
with pytest.raises(ValueError): with pytest.raises(ValueError):
s = self.ssh.Socket('localhost', 65536) self.ssh.Socket('localhost', 65536)
def test_not_connected_socket(self, virtual_socket): def test_not_connected_socket(self, virtual_socket):
sock = self.ssh.Socket('localhost', 22) sock = self.ssh.Socket('localhost', 22)
banner, header, err = sock.get_banner() banner, header, err = sock.get_banner()
assert banner is None assert banner is None
assert len(header) == 0 assert len(header) == 0
assert err == 'not connected' assert err == 'not connected'
s, e = sock.recv() s, e = sock.recv()
assert s == -1 assert s == -1
assert e == 'not connected' assert e == 'not connected'
s, e = sock.send('nothing') s, e = sock.send('nothing')
assert s == -1 assert s == -1
assert e == 'not connected' assert e == 'not connected'
s, e = sock.send_packet() s, e = sock.send_packet()
assert s == -1 assert s == -1
assert e == 'not connected' assert e == 'not connected'

View File

@ -1,287 +1,285 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pytest import pytest
# pylint: disable=line-too-long,attribute-defined-outside-init # pylint: disable=line-too-long,attribute-defined-outside-init
class TestSoftware(object): class TestSoftware:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def init(self, ssh_audit): def init(self, ssh_audit):
self.ssh = ssh_audit.SSH self.ssh = ssh_audit.SSH
def test_unknown_software(self): def test_unknown_software(self):
ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa
assert ps('SSH-1.5') is None assert ps('SSH-1.5') is None
assert ps('SSH-1.99-AlfaMegaServer') is None assert ps('SSH-1.99-AlfaMegaServer') is None
assert ps('SSH-2.0-BetaMegaServer 0.0.1') is None assert ps('SSH-2.0-BetaMegaServer 0.0.1') is None
def test_openssh_software(self): def test_openssh_software(self):
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa
# common # common
s = ps('SSH-2.0-OpenSSH_7.3') s = ps('SSH-2.0-OpenSSH_7.3')
assert s.vendor is None assert s.vendor is None
assert s.product == 'OpenSSH' assert s.product == 'OpenSSH'
assert s.version == '7.3' assert s.version == '7.3'
assert s.patch is None assert s.patch is None
assert s.os is None assert s.os is None
assert str(s) == 'OpenSSH 7.3' assert str(s) == 'OpenSSH 7.3'
assert str(s) == s.display() assert str(s) == s.display()
assert s.display(True) == str(s) assert s.display(True) == str(s)
assert s.display(False) == str(s) assert s.display(False) == str(s)
assert repr(s) == '<Software(product=OpenSSH, version=7.3)>' assert repr(s) == '<Software(product=OpenSSH, version=7.3)>'
# common, portable # common, portable
s = ps('SSH-2.0-OpenSSH_7.2p1') s = ps('SSH-2.0-OpenSSH_7.2p1')
assert s.vendor is None assert s.vendor is None
assert s.product == 'OpenSSH' assert s.product == 'OpenSSH'
assert s.version == '7.2' assert s.version == '7.2'
assert s.patch == 'p1' assert s.patch == 'p1'
assert s.os is None assert s.os is None
assert str(s) == 'OpenSSH 7.2p1' assert str(s) == 'OpenSSH 7.2p1'
assert str(s) == s.display() assert str(s) == s.display()
assert s.display(True) == str(s) assert s.display(True) == str(s)
assert s.display(False) == 'OpenSSH 7.2' assert s.display(False) == 'OpenSSH 7.2'
assert repr(s) == '<Software(product=OpenSSH, version=7.2, patch=p1)>' assert repr(s) == '<Software(product=OpenSSH, version=7.2, patch=p1)>'
# dot instead of underline # dot instead of underline
s = ps('SSH-2.0-OpenSSH.6.6') s = ps('SSH-2.0-OpenSSH.6.6')
assert s.vendor is None assert s.vendor is None
assert s.product == 'OpenSSH' assert s.product == 'OpenSSH'
assert s.version == '6.6' assert s.version == '6.6'
assert s.patch is None assert s.patch is None
assert s.os is None assert s.os is None
assert str(s) == 'OpenSSH 6.6' assert str(s) == 'OpenSSH 6.6'
assert str(s) == s.display() assert str(s) == s.display()
assert s.display(True) == str(s) assert s.display(True) == str(s)
assert s.display(False) == str(s) assert s.display(False) == str(s)
assert repr(s) == '<Software(product=OpenSSH, version=6.6)>' assert repr(s) == '<Software(product=OpenSSH, version=6.6)>'
# dash instead of underline # dash instead of underline
s = ps('SSH-2.0-OpenSSH-3.9p1') s = ps('SSH-2.0-OpenSSH-3.9p1')
assert s.vendor is None assert s.vendor is None
assert s.product == 'OpenSSH' assert s.product == 'OpenSSH'
assert s.version == '3.9' assert s.version == '3.9'
assert s.patch == 'p1' assert s.patch == 'p1'
assert s.os is None assert s.os is None
assert str(s) == 'OpenSSH 3.9p1' assert str(s) == 'OpenSSH 3.9p1'
assert str(s) == s.display() assert str(s) == s.display()
assert s.display(True) == str(s) assert s.display(True) == str(s)
assert s.display(False) == 'OpenSSH 3.9' assert s.display(False) == 'OpenSSH 3.9'
assert repr(s) == '<Software(product=OpenSSH, version=3.9, patch=p1)>' assert repr(s) == '<Software(product=OpenSSH, version=3.9, patch=p1)>'
# patch prefix with dash # patch prefix with dash
s = ps('SSH-2.0-OpenSSH_7.2-hpn14v5') s = ps('SSH-2.0-OpenSSH_7.2-hpn14v5')
assert s.vendor is None assert s.vendor is None
assert s.product == 'OpenSSH' assert s.product == 'OpenSSH'
assert s.version == '7.2' assert s.version == '7.2'
assert s.patch == 'hpn14v5' assert s.patch == 'hpn14v5'
assert s.os is None assert s.os is None
assert str(s) == 'OpenSSH 7.2 (hpn14v5)' assert str(s) == 'OpenSSH 7.2 (hpn14v5)'
assert str(s) == s.display() assert str(s) == s.display()
assert s.display(True) == str(s) assert s.display(True) == str(s)
assert s.display(False) == 'OpenSSH 7.2' assert s.display(False) == 'OpenSSH 7.2'
assert repr(s) == '<Software(product=OpenSSH, version=7.2, patch=hpn14v5)>' assert repr(s) == '<Software(product=OpenSSH, version=7.2, patch=hpn14v5)>'
# patch prefix with underline # patch prefix with underline
s = ps('SSH-1.5-OpenSSH_6.6.1_hpn13v11') s = ps('SSH-1.5-OpenSSH_6.6.1_hpn13v11')
assert s.vendor is None assert s.vendor is None
assert s.product == 'OpenSSH' assert s.product == 'OpenSSH'
assert s.version == '6.6.1' assert s.version == '6.6.1'
assert s.patch == 'hpn13v11' assert s.patch == 'hpn13v11'
assert s.os is None assert s.os is None
assert str(s) == 'OpenSSH 6.6.1 (hpn13v11)' assert str(s) == 'OpenSSH 6.6.1 (hpn13v11)'
assert str(s) == s.display() assert str(s) == s.display()
assert s.display(True) == str(s) assert s.display(True) == str(s)
assert s.display(False) == 'OpenSSH 6.6.1' assert s.display(False) == 'OpenSSH 6.6.1'
assert repr(s) == '<Software(product=OpenSSH, version=6.6.1, patch=hpn13v11)>' assert repr(s) == '<Software(product=OpenSSH, version=6.6.1, patch=hpn13v11)>'
# patch prefix with dot # patch prefix with dot
s = ps('SSH-2.0-OpenSSH_5.9.CASPUR') s = ps('SSH-2.0-OpenSSH_5.9.CASPUR')
assert s.vendor is None assert s.vendor is None
assert s.product == 'OpenSSH' assert s.product == 'OpenSSH'
assert s.version == '5.9' assert s.version == '5.9'
assert s.patch == 'CASPUR' assert s.patch == 'CASPUR'
assert s.os is None assert s.os is None
assert str(s) == 'OpenSSH 5.9 (CASPUR)' assert str(s) == 'OpenSSH 5.9 (CASPUR)'
assert str(s) == s.display() assert str(s) == s.display()
assert s.display(True) == str(s) assert s.display(True) == str(s)
assert s.display(False) == 'OpenSSH 5.9' assert s.display(False) == 'OpenSSH 5.9'
assert repr(s) == '<Software(product=OpenSSH, version=5.9, patch=CASPUR)>' assert repr(s) == '<Software(product=OpenSSH, version=5.9, patch=CASPUR)>'
def test_dropbear_software(self): def test_dropbear_software(self):
ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa
# common # common
s = ps('SSH-2.0-dropbear_2016.74') s = ps('SSH-2.0-dropbear_2016.74')
assert s.vendor is None assert s.vendor is None
assert s.product == 'Dropbear SSH' assert s.product == 'Dropbear SSH'
assert s.version == '2016.74' assert s.version == '2016.74'
assert s.patch is None assert s.patch is None
assert s.os is None assert s.os is None
assert str(s) == 'Dropbear SSH 2016.74' assert str(s) == 'Dropbear SSH 2016.74'
assert str(s) == s.display() assert str(s) == s.display()
assert s.display(True) == str(s) assert s.display(True) == str(s)
assert s.display(False) == str(s) assert s.display(False) == str(s)
assert repr(s) == '<Software(product=Dropbear SSH, version=2016.74)>' assert repr(s) == '<Software(product=Dropbear SSH, version=2016.74)>'
# common, patch # common, patch
s = ps('SSH-2.0-dropbear_0.44test4') s = ps('SSH-2.0-dropbear_0.44test4')
assert s.vendor is None assert s.vendor is None
assert s.product == 'Dropbear SSH' assert s.product == 'Dropbear SSH'
assert s.version == '0.44' assert s.version == '0.44'
assert s.patch == 'test4' assert s.patch == 'test4'
assert s.os is None assert s.os is None
assert str(s) == 'Dropbear SSH 0.44 (test4)' assert str(s) == 'Dropbear SSH 0.44 (test4)'
assert str(s) == s.display() assert str(s) == s.display()
assert s.display(True) == str(s) assert s.display(True) == str(s)
assert s.display(False) == 'Dropbear SSH 0.44' assert s.display(False) == 'Dropbear SSH 0.44'
assert repr(s) == '<Software(product=Dropbear SSH, version=0.44, patch=test4)>' assert repr(s) == '<Software(product=Dropbear SSH, version=0.44, patch=test4)>'
# patch prefix with dash # patch prefix with dash
s = ps('SSH-2.0-dropbear_0.44-Freesco-p49') s = ps('SSH-2.0-dropbear_0.44-Freesco-p49')
assert s.vendor is None assert s.vendor is None
assert s.product == 'Dropbear SSH' assert s.product == 'Dropbear SSH'
assert s.version == '0.44' assert s.version == '0.44'
assert s.patch == 'Freesco-p49' assert s.patch == 'Freesco-p49'
assert s.os is None assert s.os is None
assert str(s) == 'Dropbear SSH 0.44 (Freesco-p49)' assert str(s) == 'Dropbear SSH 0.44 (Freesco-p49)'
assert str(s) == s.display() assert str(s) == s.display()
assert s.display(True) == str(s) assert s.display(True) == str(s)
assert s.display(False) == 'Dropbear SSH 0.44' assert s.display(False) == 'Dropbear SSH 0.44'
assert repr(s) == '<Software(product=Dropbear SSH, version=0.44, patch=Freesco-p49)>' assert repr(s) == '<Software(product=Dropbear SSH, version=0.44, patch=Freesco-p49)>'
# patch prefix with underline # patch prefix with underline
s = ps('SSH-2.0-dropbear_2014.66_agbn_1') s = ps('SSH-2.0-dropbear_2014.66_agbn_1')
assert s.vendor is None assert s.vendor is None
assert s.product == 'Dropbear SSH' assert s.product == 'Dropbear SSH'
assert s.version == '2014.66' assert s.version == '2014.66'
assert s.patch == 'agbn_1' assert s.patch == 'agbn_1'
assert s.os is None assert s.os is None
assert str(s) == 'Dropbear SSH 2014.66 (agbn_1)' assert str(s) == 'Dropbear SSH 2014.66 (agbn_1)'
assert str(s) == s.display() assert str(s) == s.display()
assert s.display(True) == str(s) assert s.display(True) == str(s)
assert s.display(False) == 'Dropbear SSH 2014.66' assert s.display(False) == 'Dropbear SSH 2014.66'
assert repr(s) == '<Software(product=Dropbear SSH, version=2014.66, patch=agbn_1)>' assert repr(s) == '<Software(product=Dropbear SSH, version=2014.66, patch=agbn_1)>'
def test_libssh_software(self): def test_libssh_software(self):
ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa
# common # common
s = ps('SSH-2.0-libssh-0.2') s = ps('SSH-2.0-libssh-0.2')
assert s.vendor is None assert s.vendor is None
assert s.product == 'libssh' assert s.product == 'libssh'
assert s.version == '0.2' assert s.version == '0.2'
assert s.patch is None assert s.patch is None
assert s.os is None assert s.os is None
assert str(s) == 'libssh 0.2' assert str(s) == 'libssh 0.2'
assert str(s) == s.display() assert str(s) == s.display()
assert s.display(True) == str(s) assert s.display(True) == str(s)
assert s.display(False) == str(s) assert s.display(False) == str(s)
assert repr(s) == '<Software(product=libssh, version=0.2)>' assert repr(s) == '<Software(product=libssh, version=0.2)>'
s = ps('SSH-2.0-libssh-0.7.4') s = ps('SSH-2.0-libssh-0.7.4')
assert s.vendor is None assert s.vendor is None
assert s.product == 'libssh' assert s.product == 'libssh'
assert s.version == '0.7.4' assert s.version == '0.7.4'
assert s.patch is None assert s.patch is None
assert s.os is None assert s.os is None
assert str(s) == 'libssh 0.7.4' assert str(s) == 'libssh 0.7.4'
assert str(s) == s.display() assert str(s) == s.display()
assert s.display(True) == str(s) assert s.display(True) == str(s)
assert s.display(False) == str(s) assert s.display(False) == str(s)
assert repr(s) == '<Software(product=libssh, version=0.7.4)>' assert repr(s) == '<Software(product=libssh, version=0.7.4)>'
def test_romsshell_software(self): def test_romsshell_software(self):
ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa
# common # common
s = ps('SSH-2.0-RomSShell_5.40') s = ps('SSH-2.0-RomSShell_5.40')
assert s.vendor == 'Allegro Software' assert s.vendor == 'Allegro Software'
assert s.product == 'RomSShell' assert s.product == 'RomSShell'
assert s.version == '5.40' assert s.version == '5.40'
assert s.patch is None assert s.patch is None
assert s.os is None assert s.os is None
assert str(s) == 'Allegro Software RomSShell 5.40' assert str(s) == 'Allegro Software RomSShell 5.40'
assert str(s) == s.display() assert str(s) == s.display()
assert s.display(True) == str(s) assert s.display(True) == str(s)
assert s.display(False) == str(s) assert s.display(False) == str(s)
assert repr(s) == '<Software(vendor=Allegro Software, product=RomSShell, version=5.40)>' assert repr(s) == '<Software(vendor=Allegro Software, product=RomSShell, version=5.40)>'
def test_hp_ilo_software(self): def test_hp_ilo_software(self):
ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa
# common # common
s = ps('SSH-2.0-mpSSH_0.2.1') s = ps('SSH-2.0-mpSSH_0.2.1')
assert s.vendor == 'HP' assert s.vendor == 'HP'
assert s.product == 'iLO (Integrated Lights-Out) sshd' assert s.product == 'iLO (Integrated Lights-Out) sshd'
assert s.version == '0.2.1' assert s.version == '0.2.1'
assert s.patch is None assert s.patch is None
assert s.os is None assert s.os is None
assert str(s) == 'HP iLO (Integrated Lights-Out) sshd 0.2.1' assert str(s) == 'HP iLO (Integrated Lights-Out) sshd 0.2.1'
assert str(s) == s.display() assert str(s) == s.display()
assert s.display(True) == str(s) assert s.display(True) == str(s)
assert s.display(False) == str(s) assert s.display(False) == str(s)
assert repr(s) == '<Software(vendor=HP, product=iLO (Integrated Lights-Out) sshd, version=0.2.1)>' assert repr(s) == '<Software(vendor=HP, product=iLO (Integrated Lights-Out) sshd, version=0.2.1)>'
def test_cisco_software(self): def test_cisco_software(self):
ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa
# common # common
s = ps('SSH-1.5-Cisco-1.25') s = ps('SSH-1.5-Cisco-1.25')
assert s.vendor == 'Cisco' assert s.vendor == 'Cisco'
assert s.product == 'IOS/PIX sshd' assert s.product == 'IOS/PIX sshd'
assert s.version == '1.25' assert s.version == '1.25'
assert s.patch is None assert s.patch is None
assert s.os is None assert s.os is None
assert str(s) == 'Cisco IOS/PIX sshd 1.25' assert str(s) == 'Cisco IOS/PIX sshd 1.25'
assert str(s) == s.display() assert str(s) == s.display()
assert s.display(True) == str(s) assert s.display(True) == str(s)
assert s.display(False) == str(s) assert s.display(False) == str(s)
assert repr(s) == '<Software(vendor=Cisco, product=IOS/PIX sshd, version=1.25)>' assert repr(s) == '<Software(vendor=Cisco, product=IOS/PIX sshd, version=1.25)>'
def test_software_os(self): def test_software_os(self):
ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa
# unknown # unknown
s = ps('SSH-2.0-OpenSSH_3.7.1 MegaOperatingSystem 123') s = ps('SSH-2.0-OpenSSH_3.7.1 MegaOperatingSystem 123')
assert s.os is None assert s.os is None
# NetBSD # NetBSD
s = ps('SSH-1.99-OpenSSH_2.5.1 NetBSD_Secure_Shell-20010614') s = ps('SSH-1.99-OpenSSH_2.5.1 NetBSD_Secure_Shell-20010614')
assert s.os == 'NetBSD (2001-06-14)' assert s.os == 'NetBSD (2001-06-14)'
assert str(s) == 'OpenSSH 2.5.1 running on NetBSD (2001-06-14)' assert str(s) == 'OpenSSH 2.5.1 running on NetBSD (2001-06-14)'
assert repr(s) == '<Software(product=OpenSSH, version=2.5.1, os=NetBSD (2001-06-14))>' assert repr(s) == '<Software(product=OpenSSH, version=2.5.1, os=NetBSD (2001-06-14))>'
s = ps('SSH-1.99-OpenSSH_5.0 NetBSD_Secure_Shell-20080403+-hpn13v1') s = ps('SSH-1.99-OpenSSH_5.0 NetBSD_Secure_Shell-20080403+-hpn13v1')
assert s.os == 'NetBSD (2008-04-03)' assert s.os == 'NetBSD (2008-04-03)'
assert str(s) == 'OpenSSH 5.0 running on NetBSD (2008-04-03)' assert str(s) == 'OpenSSH 5.0 running on NetBSD (2008-04-03)'
assert repr(s) == '<Software(product=OpenSSH, version=5.0, os=NetBSD (2008-04-03))>' assert repr(s) == '<Software(product=OpenSSH, version=5.0, os=NetBSD (2008-04-03))>'
s = ps('SSH-2.0-OpenSSH_6.6.1_hpn13v11 NetBSD-20100308') s = ps('SSH-2.0-OpenSSH_6.6.1_hpn13v11 NetBSD-20100308')
assert s.os == 'NetBSD (2010-03-08)' assert s.os == 'NetBSD (2010-03-08)'
assert str(s) == 'OpenSSH 6.6.1 (hpn13v11) running on NetBSD (2010-03-08)' assert str(s) == 'OpenSSH 6.6.1 (hpn13v11) running on NetBSD (2010-03-08)'
assert repr(s) == '<Software(product=OpenSSH, version=6.6.1, patch=hpn13v11, os=NetBSD (2010-03-08))>' assert repr(s) == '<Software(product=OpenSSH, version=6.6.1, patch=hpn13v11, os=NetBSD (2010-03-08))>'
s = ps('SSH-2.0-OpenSSH_4.4 NetBSD') s = ps('SSH-2.0-OpenSSH_4.4 NetBSD')
assert s.os == 'NetBSD' assert s.os == 'NetBSD'
assert str(s) == 'OpenSSH 4.4 running on NetBSD' assert str(s) == 'OpenSSH 4.4 running on NetBSD'
assert repr(s) == '<Software(product=OpenSSH, version=4.4, os=NetBSD)>' assert repr(s) == '<Software(product=OpenSSH, version=4.4, os=NetBSD)>'
s = ps('SSH-2.0-OpenSSH_3.0.2 NetBSD Secure Shell') s = ps('SSH-2.0-OpenSSH_3.0.2 NetBSD Secure Shell')
assert s.os == 'NetBSD' assert s.os == 'NetBSD'
assert str(s) == 'OpenSSH 3.0.2 running on NetBSD' assert str(s) == 'OpenSSH 3.0.2 running on NetBSD'
assert repr(s) == '<Software(product=OpenSSH, version=3.0.2, os=NetBSD)>' assert repr(s) == '<Software(product=OpenSSH, version=3.0.2, os=NetBSD)>'
# FreeBSD # FreeBSD
s = ps('SSH-2.0-OpenSSH_7.2 FreeBSD-20160310') s = ps('SSH-2.0-OpenSSH_7.2 FreeBSD-20160310')
assert s.os == 'FreeBSD (2016-03-10)' assert s.os == 'FreeBSD (2016-03-10)'
assert str(s) == 'OpenSSH 7.2 running on FreeBSD (2016-03-10)' assert str(s) == 'OpenSSH 7.2 running on FreeBSD (2016-03-10)'
assert repr(s) == '<Software(product=OpenSSH, version=7.2, os=FreeBSD (2016-03-10))>' assert repr(s) == '<Software(product=OpenSSH, version=7.2, os=FreeBSD (2016-03-10))>'
s = ps('SSH-1.99-OpenSSH_2.9 FreeBSD localisations 20020307') s = ps('SSH-1.99-OpenSSH_2.9 FreeBSD localisations 20020307')
assert s.os == 'FreeBSD (2002-03-07)' assert s.os == 'FreeBSD (2002-03-07)'
assert str(s) == 'OpenSSH 2.9 running on FreeBSD (2002-03-07)' assert str(s) == 'OpenSSH 2.9 running on FreeBSD (2002-03-07)'
assert repr(s) == '<Software(product=OpenSSH, version=2.9, os=FreeBSD (2002-03-07))>' assert repr(s) == '<Software(product=OpenSSH, version=2.9, os=FreeBSD (2002-03-07))>'
s = ps('SSH-2.0-OpenSSH_2.3.0 green@FreeBSD.org 20010321') s = ps('SSH-2.0-OpenSSH_2.3.0 green@FreeBSD.org 20010321')
assert s.os == 'FreeBSD (2001-03-21)' assert s.os == 'FreeBSD (2001-03-21)'
assert str(s) == 'OpenSSH 2.3.0 running on FreeBSD (2001-03-21)' assert str(s) == 'OpenSSH 2.3.0 running on FreeBSD (2001-03-21)'
assert repr(s) == '<Software(product=OpenSSH, version=2.3.0, os=FreeBSD (2001-03-21))>' assert repr(s) == '<Software(product=OpenSSH, version=2.3.0, os=FreeBSD (2001-03-21))>'
s = ps('SSH-1.99-OpenSSH_4.4p1 FreeBSD-openssh-portable-overwrite-base-4.4.p1_1,1') s = ps('SSH-1.99-OpenSSH_4.4p1 FreeBSD-openssh-portable-overwrite-base-4.4.p1_1,1')
assert s.os == 'FreeBSD' assert s.os == 'FreeBSD'
assert str(s) == 'OpenSSH 4.4p1 running on FreeBSD' assert str(s) == 'OpenSSH 4.4p1 running on FreeBSD'
assert repr(s) == '<Software(product=OpenSSH, version=4.4, patch=p1, os=FreeBSD)>' assert repr(s) == '<Software(product=OpenSSH, version=4.4, patch=p1, os=FreeBSD)>'
s = ps('SSH-2.0-OpenSSH_7.2-OVH-rescue FreeBSD') s = ps('SSH-2.0-OpenSSH_7.2-OVH-rescue FreeBSD')
assert s.os == 'FreeBSD' assert s.os == 'FreeBSD'
assert str(s) == 'OpenSSH 7.2 (OVH-rescue) running on FreeBSD' assert str(s) == 'OpenSSH 7.2 (OVH-rescue) running on FreeBSD'
assert repr(s) == '<Software(product=OpenSSH, version=7.2, patch=OVH-rescue, os=FreeBSD)>' assert repr(s) == '<Software(product=OpenSSH, version=7.2, patch=OVH-rescue, os=FreeBSD)>'
# Windows # Windows
s = ps('SSH-2.0-OpenSSH_3.7.1 in RemotelyAnywhere 5.21.422') s = ps('SSH-2.0-OpenSSH_3.7.1 in RemotelyAnywhere 5.21.422')
assert s.os == 'Microsoft Windows (RemotelyAnywhere 5.21.422)' assert s.os == 'Microsoft Windows (RemotelyAnywhere 5.21.422)'
assert str(s) == 'OpenSSH 3.7.1 running on Microsoft Windows (RemotelyAnywhere 5.21.422)' assert str(s) == 'OpenSSH 3.7.1 running on Microsoft Windows (RemotelyAnywhere 5.21.422)'
assert repr(s) == '<Software(product=OpenSSH, version=3.7.1, os=Microsoft Windows (RemotelyAnywhere 5.21.422))>' assert repr(s) == '<Software(product=OpenSSH, version=3.7.1, os=Microsoft Windows (RemotelyAnywhere 5.21.422))>'
s = ps('SSH-2.0-OpenSSH_3.8 in DesktopAuthority 7.1.091') s = ps('SSH-2.0-OpenSSH_3.8 in DesktopAuthority 7.1.091')
assert s.os == 'Microsoft Windows (DesktopAuthority 7.1.091)' assert s.os == 'Microsoft Windows (DesktopAuthority 7.1.091)'
assert str(s) == 'OpenSSH 3.8 running on Microsoft Windows (DesktopAuthority 7.1.091)' assert str(s) == 'OpenSSH 3.8 running on Microsoft Windows (DesktopAuthority 7.1.091)'
assert repr(s) == '<Software(product=OpenSSH, version=3.8, os=Microsoft Windows (DesktopAuthority 7.1.091))>' assert repr(s) == '<Software(product=OpenSSH, version=3.8, os=Microsoft Windows (DesktopAuthority 7.1.091))>'
s = ps('SSH-2.0-OpenSSH_3.8 in RemoteSupportManager 1.0.023') s = ps('SSH-2.0-OpenSSH_3.8 in RemoteSupportManager 1.0.023')
assert s.os == 'Microsoft Windows (RemoteSupportManager 1.0.023)' assert s.os == 'Microsoft Windows (RemoteSupportManager 1.0.023)'
assert str(s) == 'OpenSSH 3.8 running on Microsoft Windows (RemoteSupportManager 1.0.023)' assert str(s) == 'OpenSSH 3.8 running on Microsoft Windows (RemoteSupportManager 1.0.023)'
assert repr(s) == '<Software(product=OpenSSH, version=3.8, os=Microsoft Windows (RemoteSupportManager 1.0.023))>' assert repr(s) == '<Software(product=OpenSSH, version=3.8, os=Microsoft Windows (RemoteSupportManager 1.0.023))>'

View File

@ -1,156 +1,154 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import struct import struct
import pytest import pytest
# pylint: disable=line-too-long,attribute-defined-outside-init # pylint: disable=line-too-long,attribute-defined-outside-init
class TestSSH1(object): class TestSSH1:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def init(self, ssh_audit): def init(self, ssh_audit):
self.ssh = ssh_audit.SSH self.ssh = ssh_audit.SSH
self.ssh1 = ssh_audit.SSH1 self.ssh1 = ssh_audit.SSH1
self.rbuf = ssh_audit.ReadBuf self.rbuf = ssh_audit.ReadBuf
self.wbuf = ssh_audit.WriteBuf self.wbuf = ssh_audit.WriteBuf
self.audit = ssh_audit.audit self.audit = ssh_audit.audit
self.AuditConf = ssh_audit.AuditConf self.AuditConf = ssh_audit.AuditConf
def _conf(self): def _conf(self):
conf = self.AuditConf('localhost', 22) conf = self.AuditConf('localhost', 22)
conf.colors = False conf.colors = False
conf.batch = True conf.batch = True
conf.verbose = True conf.verbose = True
conf.ssh1 = True conf.ssh1 = True
conf.ssh2 = False conf.ssh2 = False
return conf return conf
def _create_ssh1_packet(self, payload, valid_crc=True): def _create_ssh1_packet(self, payload, valid_crc=True):
padding = -(len(payload) + 4) % 8 padding = -(len(payload) + 4) % 8
plen = len(payload) + 4 plen = len(payload) + 4
pad_bytes = b'\x00' * padding pad_bytes = b'\x00' * padding
cksum = self.ssh1.crc32(pad_bytes + payload) if valid_crc else 0 cksum = self.ssh1.crc32(pad_bytes + payload) if valid_crc else 0
data = struct.pack('>I', plen) + pad_bytes + payload + struct.pack('>I', cksum) data = struct.pack('>I', plen) + pad_bytes + payload + struct.pack('>I', cksum)
return data return data
@classmethod @classmethod
def _server_key(cls): def _server_key(cls):
return (1024, 0x10001, 0xee6552da432e0ac2c422df1a51287507748bfe3b5e3e4fa989a8f49fdc163a17754939ef18ef8a667ea3b71036a151fcd7f5e01ceef1e4439864baf3ac569047582c69d6c128212e0980dcb3168f00d371004039983f6033cd785b8b8f85096c7d9405cbfdc664e27c966356a6b4eb6ee20ad43414b50de18b22829c1880b551) return (1024, 0x10001, 0xee6552da432e0ac2c422df1a51287507748bfe3b5e3e4fa989a8f49fdc163a17754939ef18ef8a667ea3b71036a151fcd7f5e01ceef1e4439864baf3ac569047582c69d6c128212e0980dcb3168f00d371004039983f6033cd785b8b8f85096c7d9405cbfdc664e27c966356a6b4eb6ee20ad43414b50de18b22829c1880b551)
@classmethod @classmethod
def _host_key(cls): def _host_key(cls):
return (2048, 0x10001, 0xdfa20cd2a530ccc8c870aa60d9feb3b35deeab81c3215a96557abbd683d21f4600f38e475d87100da9a4404220eeb3bb5584e5a2b5b48ffda58530ea19104a32577d7459d91e76aa711b241050f4cc6d5327ccce254f371acad3be56d46eb5919b73f20dbdb1177b700f00891c5bf4ed128bb90ed541b778288285bcfa28432ab5cbcb8321b6e24760e998e0daa519f093a631e44276d7dd252ce0c08c75e2ab28a7349ead779f97d0f20a6d413bf3623cd216dc35375f6366690bcc41e3b2d5465840ec7ee0dc7e3f1c101d674a0c7dbccbc3942788b111396add2f8153b46a0e4b50d66e57ee92958f1c860dd97cc0e40e32febff915343ed53573142bdf4b) return (2048, 0x10001, 0xdfa20cd2a530ccc8c870aa60d9feb3b35deeab81c3215a96557abbd683d21f4600f38e475d87100da9a4404220eeb3bb5584e5a2b5b48ffda58530ea19104a32577d7459d91e76aa711b241050f4cc6d5327ccce254f371acad3be56d46eb5919b73f20dbdb1177b700f00891c5bf4ed128bb90ed541b778288285bcfa28432ab5cbcb8321b6e24760e998e0daa519f093a631e44276d7dd252ce0c08c75e2ab28a7349ead779f97d0f20a6d413bf3623cd216dc35375f6366690bcc41e3b2d5465840ec7ee0dc7e3f1c101d674a0c7dbccbc3942788b111396add2f8153b46a0e4b50d66e57ee92958f1c860dd97cc0e40e32febff915343ed53573142bdf4b)
def _pkm_payload(self): def _pkm_payload(self):
w = self.wbuf() w = self.wbuf()
w.write(b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff') w.write(b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff')
b, e, m = self._server_key() b, e, m = self._server_key()
w.write_int(b).write_mpint1(e).write_mpint1(m) w.write_int(b).write_mpint1(e).write_mpint1(m)
b, e, m = self._host_key() b, e, m = self._host_key()
w.write_int(b).write_mpint1(e).write_mpint1(m) w.write_int(b).write_mpint1(e).write_mpint1(m)
w.write_int(2) w.write_int(2)
w.write_int(72) w.write_int(72)
w.write_int(36) w.write_int(36)
return w.write_flush() return w.write_flush()
def test_crc32(self): def test_crc32(self):
assert self.ssh1.crc32(b'') == 0x00 assert self.ssh1.crc32(b'') == 0x00
assert self.ssh1.crc32(b'The quick brown fox jumps over the lazy dog') == 0xb9c60808 assert self.ssh1.crc32(b'The quick brown fox jumps over the lazy dog') == 0xb9c60808
def test_fingerprint(self): def test_fingerprint(self):
# pylint: disable=protected-access # pylint: disable=protected-access
b, e, m = self._host_key() b, e, m = self._host_key()
fpd = self.wbuf._create_mpint(m, False) fpd = self.wbuf._create_mpint(m, False)
fpd += self.wbuf._create_mpint(e, False) fpd += self.wbuf._create_mpint(e, False)
fp = self.ssh.Fingerprint(fpd) fp = self.ssh.Fingerprint(fpd)
assert b == 2048 assert b == 2048
assert fp.md5 == 'MD5:9d:26:f8:39:fc:20:9d:9b:ca:cc:4a:0f:e1:93:f5:96' assert fp.md5 == 'MD5:9d:26:f8:39:fc:20:9d:9b:ca:cc:4a:0f:e1:93:f5:96'
assert fp.sha256 == 'SHA256:vZdx3mhzbvVJmn08t/ruv8WDhJ9jfKYsCTuSzot+QIs' assert fp.sha256 == 'SHA256:vZdx3mhzbvVJmn08t/ruv8WDhJ9jfKYsCTuSzot+QIs'
def _assert_pkm_keys(self, pkm, skey, hkey): def _assert_pkm_keys(self, pkm, skey, hkey):
b, e, m = skey b, e, m = skey
assert pkm.server_key_bits == b assert pkm.server_key_bits == b
assert pkm.server_key_public_exponent == e assert pkm.server_key_public_exponent == e
assert pkm.server_key_public_modulus == m assert pkm.server_key_public_modulus == m
b, e, m = hkey b, e, m = hkey
assert pkm.host_key_bits == b assert pkm.host_key_bits == b
assert pkm.host_key_public_exponent == e assert pkm.host_key_public_exponent == e
assert pkm.host_key_public_modulus == m assert pkm.host_key_public_modulus == m
def _assert_pkm_fields(self, pkm, skey, hkey): def _assert_pkm_fields(self, pkm, skey, hkey):
assert pkm is not None assert pkm is not None
assert pkm.cookie == b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff' assert pkm.cookie == b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
self._assert_pkm_keys(pkm, skey, hkey) self._assert_pkm_keys(pkm, skey, hkey)
assert pkm.protocol_flags == 2 assert pkm.protocol_flags == 2
assert pkm.supported_ciphers_mask == 72 assert pkm.supported_ciphers_mask == 72
assert pkm.supported_ciphers == ['3des', 'blowfish'] assert pkm.supported_ciphers == ['3des', 'blowfish']
assert pkm.supported_authentications_mask == 36 assert pkm.supported_authentications_mask == 36
assert pkm.supported_authentications == ['rsa', 'tis'] assert pkm.supported_authentications == ['rsa', 'tis']
fp = self.ssh.Fingerprint(pkm.host_key_fingerprint_data) fp = self.ssh.Fingerprint(pkm.host_key_fingerprint_data)
assert fp.md5 == 'MD5:9d:26:f8:39:fc:20:9d:9b:ca:cc:4a:0f:e1:93:f5:96' assert fp.md5 == 'MD5:9d:26:f8:39:fc:20:9d:9b:ca:cc:4a:0f:e1:93:f5:96'
assert fp.sha256 == 'SHA256:vZdx3mhzbvVJmn08t/ruv8WDhJ9jfKYsCTuSzot+QIs' assert fp.sha256 == 'SHA256:vZdx3mhzbvVJmn08t/ruv8WDhJ9jfKYsCTuSzot+QIs'
def test_pkm_init(self): def test_pkm_init(self):
cookie = b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff' cookie = b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
pflags, cmask, amask = 2, 72, 36 pflags, cmask, amask = 2, 72, 36
skey, hkey = self._server_key(), self._host_key() skey, hkey = self._server_key(), self._host_key()
pkm = self.ssh1.PublicKeyMessage(cookie, skey, hkey, pflags, cmask, amask) pkm = self.ssh1.PublicKeyMessage(cookie, skey, hkey, pflags, cmask, amask)
self._assert_pkm_fields(pkm, skey, hkey) self._assert_pkm_fields(pkm, skey, hkey)
for skey2 in ([], [0], [0,1], [0,1,2,3]): for skey2 in ([], [0], [0, 1], [0, 1, 2, 3]):
with pytest.raises(ValueError): with pytest.raises(ValueError):
pkm = self.ssh1.PublicKeyMessage(cookie, skey2, hkey, pflags, cmask, amask) pkm = self.ssh1.PublicKeyMessage(cookie, skey2, hkey, pflags, cmask, amask)
for hkey2 in ([], [0], [0,1], [0,1,2,3]): for hkey2 in ([], [0], [0, 1], [0, 1, 2, 3]):
with pytest.raises(ValueError): with pytest.raises(ValueError):
print(hkey2) print(hkey2)
pkm = self.ssh1.PublicKeyMessage(cookie, skey, hkey2, pflags, cmask, amask) pkm = self.ssh1.PublicKeyMessage(cookie, skey, hkey2, pflags, cmask, amask)
def test_pkm_read(self): def test_pkm_read(self):
pkm = self.ssh1.PublicKeyMessage.parse(self._pkm_payload()) pkm = self.ssh1.PublicKeyMessage.parse(self._pkm_payload())
self._assert_pkm_fields(pkm, self._server_key(), self._host_key()) self._assert_pkm_fields(pkm, self._server_key(), self._host_key())
def test_pkm_payload(self): def test_pkm_payload(self):
cookie = b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff' cookie = b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
skey, hkey = self._server_key(), self._host_key() skey, hkey = self._server_key(), self._host_key()
pflags, cmask, amask = 2, 72, 36 pflags, cmask, amask = 2, 72, 36
pkm1 = self.ssh1.PublicKeyMessage(cookie, skey, hkey, pflags, cmask, amask) pkm1 = self.ssh1.PublicKeyMessage(cookie, skey, hkey, pflags, cmask, amask)
pkm2 = self.ssh1.PublicKeyMessage.parse(self._pkm_payload()) pkm2 = self.ssh1.PublicKeyMessage.parse(self._pkm_payload())
assert pkm1.payload == pkm2.payload assert pkm1.payload == pkm2.payload
def test_ssh1_server_simple(self, output_spy, virtual_socket): def test_ssh1_server_simple(self, output_spy, virtual_socket):
vsocket = virtual_socket vsocket = virtual_socket
w = self.wbuf() w = self.wbuf()
w.write_byte(self.ssh.Protocol.SMSG_PUBLIC_KEY) w.write_byte(self.ssh.Protocol.SMSG_PUBLIC_KEY)
w.write(self._pkm_payload()) w.write(self._pkm_payload())
vsocket.rdata.append(b'SSH-1.5-OpenSSH_7.2 ssh-audit-test\r\n') vsocket.rdata.append(b'SSH-1.5-OpenSSH_7.2 ssh-audit-test\r\n')
vsocket.rdata.append(self._create_ssh1_packet(w.write_flush())) vsocket.rdata.append(self._create_ssh1_packet(w.write_flush()))
output_spy.begin() output_spy.begin()
self.audit(self._conf()) self.audit(self._conf())
lines = output_spy.flush() lines = output_spy.flush()
assert len(lines) == 13 assert len(lines) == 13
def test_ssh1_server_invalid_first_packet(self, output_spy, virtual_socket): def test_ssh1_server_invalid_first_packet(self, output_spy, virtual_socket):
vsocket = virtual_socket vsocket = virtual_socket
w = self.wbuf() w = self.wbuf()
w.write_byte(self.ssh.Protocol.SMSG_PUBLIC_KEY + 1) w.write_byte(self.ssh.Protocol.SMSG_PUBLIC_KEY + 1)
w.write(self._pkm_payload()) w.write(self._pkm_payload())
vsocket.rdata.append(b'SSH-1.5-OpenSSH_7.2 ssh-audit-test\r\n') vsocket.rdata.append(b'SSH-1.5-OpenSSH_7.2 ssh-audit-test\r\n')
vsocket.rdata.append(self._create_ssh1_packet(w.write_flush())) vsocket.rdata.append(self._create_ssh1_packet(w.write_flush()))
output_spy.begin() output_spy.begin()
with pytest.raises(SystemExit): ret = self.audit(self._conf())
self.audit(self._conf()) assert ret != 0
lines = output_spy.flush() lines = output_spy.flush()
assert len(lines) == 7 assert len(lines) == 7
assert 'unknown message' in lines[-1] assert 'unknown message' in lines[-1]
def test_ssh1_server_invalid_checksum(self, output_spy, virtual_socket): def test_ssh1_server_invalid_checksum(self, output_spy, virtual_socket):
vsocket = virtual_socket vsocket = virtual_socket
w = self.wbuf() w = self.wbuf()
w.write_byte(self.ssh.Protocol.SMSG_PUBLIC_KEY + 1) w.write_byte(self.ssh.Protocol.SMSG_PUBLIC_KEY + 1)
w.write(self._pkm_payload()) w.write(self._pkm_payload())
vsocket.rdata.append(b'SSH-1.5-OpenSSH_7.2 ssh-audit-test\r\n') vsocket.rdata.append(b'SSH-1.5-OpenSSH_7.2 ssh-audit-test\r\n')
vsocket.rdata.append(self._create_ssh1_packet(w.write_flush(), False)) vsocket.rdata.append(self._create_ssh1_packet(w.write_flush(), False))
output_spy.begin() output_spy.begin()
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
self.audit(self._conf()) self.audit(self._conf())
lines = output_spy.flush() lines = output_spy.flush()
assert len(lines) == 1 assert len(lines) == 1
assert 'checksum' in lines[-1] assert 'checksum' in lines[-1]

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