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:


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.


  • 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.


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.

  • 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 9: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 9:40
  • 1
    @EricDuminil Simply use a laptop with the same IP and wireshark. For FTP you could use pyftpdlib: 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. Thanks again! – Eric Duminil Apr 4 '18 at 14:19

@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'
        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()
            # 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)
                        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)

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.