-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
ssh-fingerprint
check: `Verifies if remote host fingerprint mat…
…ches. Helps detecting man-in-the-middle and server takeover attacks.`
- Loading branch information
1 parent
4a97f80
commit fd2c9d1
Showing
2 changed files
with
106 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
#!/usr/bin/env python3 | ||
|
||
""" | ||
ssh-fingerprint | ||
--------------- | ||
Verifies if remote host fingerprint matches. Helps detecting man-in-the-middle and server takeover attacks. | ||
Parameters: | ||
- EXPECTED_FINGERPRINT (example: zsp.net.pl ssh-rsa SOMESOMESOMESOMESOMEKEYHERE) | ||
- METHOD (default: rsa) | ||
- HOST (example: zsp.net.pl) | ||
- PORT (example: 22) | ||
""" | ||
|
||
import subprocess | ||
import os | ||
import sys | ||
|
||
|
||
class SshFingerprintScan: | ||
def main(self, expected_fingerprint: str, method: str, domain: str, port: int) -> tuple: | ||
if expected_fingerprint == '': | ||
return 'You need to provide a EXPECTED_FINGERPRINT', False | ||
|
||
if method == '': | ||
return 'You need to provide a METHOD', False | ||
|
||
if domain == '': | ||
return 'You need to provide a HOST', False | ||
|
||
fingerprint = self._get_fingerprint(domain, method, port) | ||
|
||
if fingerprint.strip() == expected_fingerprint.strip(): | ||
return "Fingerprint is OK", True | ||
|
||
return "Fingerprint does not match. Expected: %s, got: %s" % (expected_fingerprint, fingerprint), False | ||
|
||
@staticmethod | ||
def _get_fingerprint(domain: str, method: str, port: int) -> str: | ||
out = subprocess.check_output(['ssh-keyscan', '-t', method, '-p', str(port), domain], stderr=subprocess.DEVNULL) | ||
return out.decode('utf-8') | ||
|
||
|
||
if __name__ == '__main__': | ||
app = SshFingerprintScan() | ||
text, status = app.main( | ||
expected_fingerprint=os.getenv('EXPECTED_FINGERPRINT', ''), | ||
method=os.getenv('METHOD', 'rsa'), | ||
domain=os.getenv('HOST', ''), | ||
port=int(os.getenv('PORT', 22)) | ||
) | ||
|
||
print(text) | ||
sys.exit(0 if status else 1) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import unittest | ||
import docker | ||
|
||
from .utils import run_check | ||
from .utils.ssh_test import TestThatRequiresSshServer | ||
|
||
|
||
class SshFingerprintTest(TestThatRequiresSshServer, unittest.TestCase): | ||
docker_client: docker.DockerClient | ||
|
||
def test_success_case(self): | ||
stdout: str | ||
result: int | ||
|
||
# we create SSH server for testing dynamically in a docker container, so each time it has a different identity | ||
current_expected_fingerprint = self.get_current_ssh_server_fingerprint() | ||
|
||
stdout, result, hooks_output = run_check('ssh-fingerprint', { | ||
'HOST': 'localhost', | ||
'PORT': 3222, | ||
'EXPECTED_FINGERPRINT': current_expected_fingerprint.decode('utf-8') | ||
}, {}) | ||
|
||
self.assertEqual('Fingerprint is OK', stdout.strip()) | ||
self.assertTrue(result) | ||
|
||
def test_invalid_fingerprint(self): | ||
stdout: str | ||
result: int | ||
|
||
stdout, result, hooks_output = run_check('ssh-fingerprint', { | ||
'HOST': 'localhost', | ||
'PORT': 3222, | ||
'EXPECTED_FINGERPRINT': 'SOME FINGERPRINT THAT DOES NOT MATCH SERVER FINGERPRINT' | ||
}, {}) | ||
|
||
self.assertIn('Fingerprint does not match', stdout.strip()) | ||
self.assertFalse(result) | ||
|
||
def test_missing_host_parameter(self): | ||
stdout: str | ||
result: int | ||
|
||
stdout, result, hooks_output = run_check('ssh-fingerprint', { | ||
'PORT': 3222, | ||
'EXPECTED_FINGERPRINT': 'SOME FINGERPRINT THAT DOES NOT MATCH SERVER FINGERPRINT' | ||
}, {}) | ||
|
||
self.assertIn('You need to provide a HOST', stdout.strip()) | ||
self.assertFalse(result) |