0

Problem

Some old WS_FTP server stopped booting at work after a Windows update. The corresponding admin is long gone. The only information I have is :

  • file structure
  • user names
  • unsalted SHA256 hash for each user

One of those hashes is:

5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8

which corresponds to "password". I could check with : echo -n password | sha256sum.

I don't even know where the FTP clients are. Some of them are remote sensors to which I cannot get access. They were still sending data until the server stopped. The data isn't confidential but important to us nonetheless.

Attempts

  • I tried running john the ripper on the hashes. It found 4 passwords out of 30.

  • I tried to find a Linux FTP server which uses unsalted SHA256 hashes. I think it's too insecure so no server proposed it, at least not as default

  • Some servers (e.g. vsftpd) delegate to htpasswd. I couldn't find a way to save an unsalted SHA256 hash, though.

Question

Is it possible to create a htpasswd hash with SHA256, without salt and only one round? The minimum seems to be a 8-byte salt and 1000 rounds with mkpasswd.

Is there another linux FTP server which can be configured to work with thoses hashes?

I don't care much about security, I'd just like to set-up an FTP server which accepts incoming connections from the sensors.

Eric Duminil
  • 101
  • 3
  • 1
    You might simply install a replacement FTP server, set up the accounts with the passwords you do know and use a network sniffer to collect the (failing) passwords of the accounts that you are missing. – HBruijn Mar 20 '18 at 09:32
  • @HBruijn: That could work, yes. Thanks for the comment. Do you know any "fake" FTP-server which simply dumps the failed passwords? – Eric Duminil Mar 20 '18 at 09:40
  • 1
    @EricDuminil Simply use a laptop with the same IP and wireshark. For FTP you could use pyftpdlib: https://pypi.python.org/pypi/pyftpdlib/ – Broco Mar 20 '18 at 11:31
  • @Broco: pyftpdlib might be just what I need, thanks. I'll try it and comment back. – Eric Duminil Mar 20 '18 at 16:13
  • @Broco: It worked perfectly. See [here](https://serverfault.com/questions/903533/linux-ftp-server-which-can-work-with-unsalted-sha256-hashes/905974#905974). Thanks again! – Eric Duminil Apr 04 '18 at 14:19

1 Answers1

0

@Broco suggested to use pyftpdlib, which worked perfectly for my needs!

Here are the steps:

  • Install a Linux server
  • Install Anaconda
  • Install pyftpdlib
  • Create a json file with a dict of username, hash and folder
  • Create a pyftpdlib script for an FTP server which compares the SHA256 hashes
  • Redirect port 21 to port 8021
  • Run it as a systemd unit as a non-privileged user
  • Restart the ftp server automatically if the json file is modified.

Here's a template for the json file:

{
    "user1": {
        "folder": "users/user1",
        "sha256": "DFB0CE07EDF923F1F40BA56CC9BA9C396B53E3399E3164D60E35050BAA2BE9C9"
    },
    "user2": {
        "folder": "users/user2",
        "sha256": "5E884898DA28047151D0E56F8DC6292773603D0D6AABBDD62A11EF721D1542D8"
    }
}

Here's the script:

#!/opt/anaconda3/bin/python -u
#encoding: UTF-8
import re
import os
import hashlib
import json
from pathlib import Path
from pyftpdlib.authorizers import DummyAuthorizer, AuthenticationFailed
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
from pyftpdlib.filesystems import AbstractedFS

FTP_FOLDER = Path("/media/ftp_data")

with open('input/ftp_users.json') as users_json:
    USERS = json.loads(users_json.read())

current_folder = Path.cwd().resolve()


def login_dump(username, password, success):
    if success:
        subfolder = 'logins'
        attr = 'w'
    else:
        subfolder = 'logins/failed'
        attr = 'a'
    logins_folder = current_folder.joinpath(subfolder)
    logins_folder.mkdir(parents=True, exist_ok=True)
    with open(str(logins_folder.joinpath(username)), attr) as user_file:
        # NOTE: Could write better hash directly, e.g. with `mkpasswd -m sha-512`
        user_file.write(password + "\n")


class SHA256Authorizer(DummyAuthorizer):
    def validate_authentication(self, username, password, handler):
        sha256_hash = hashlib.sha256(password.encode('ascii')).hexdigest().upper()
        try:
            # NOTE: Case sensitive!
            if self.user_table[username]['pwd'] != sha256_hash:
                login_dump(username, password, False)
                raise AuthenticationFailed
        except KeyError:
            login_dump(username, password, False)
            raise AuthenticationFailed
        login_dump(username, password, True)


authorizer = SHA256Authorizer()

for user, params in USERS.items():
    print("Adding user %r" % user)
    folder = FTP_FOLDER.joinpath(params['folder'])
    folder.mkdir(parents=True, exist_ok=True)
    authorizer.add_user(user,
                        params['sha256'].upper(),
                        str(folder),
                        perm="elradfmw",
                        msg_login="Welcome, %s!" % user)
handler = FTPHandler
handler.authorizer = authorizer
handler.banner = "FTP server"


class WindowsOrUnixPathFS(AbstractedFS):
    def ftpnorm(self, ftppath):
        # NOTE: Some old clients still think they talk to a Windows Server
        return super().ftpnorm(ftppath.replace("\\", "/"))


handler.abstracted_fs = WindowsOrUnixPathFS

handler.passive_ports = range(40000, 40500)
# NOTE: Port forwarding is needed because this script shouldn't be run as root.
# See https://serverfault.com/a/238565/442344
server = FTPServer((IP_ADDRESS, 8021), handler)
server.serve_forever()

The script logs the passwords in plain text, which is acceptable because the data isn't confidential. After a few months, I'll switch to vsftpd.

Eric Duminil
  • 101
  • 3