How to Configure Chroot SFTP to Allow Direct File Uploads to Root Directory Without CD Command


2 views

When implementing chrooted SFTP environments, we often encounter a frustrating limitation: the chroot directory itself (typically /home/user) must be owned by root with strict permissions (755), while users need a subdirectory for actual file operations. This creates the common workflow where users must cd into a subfolder before uploading files.

The SSH daemon enforces these security constraints:

# Required permissions for chroot directory
drwxr-xr-x 5 root root 4096 May 4 11:24 /home/sftpuser

The root ownership prevents users from writing directly to the chroot root, hence the "Permission denied" error when attempting:

sftp> put testfile.txt
Uploading testfile.txt to /testfile.txt
remote open("/testfile.txt"): Permission denied

We can achieve direct root uploads while maintaining security through these steps:

  1. Create a writable directory elsewhere:
  2. mkdir -p /srv/sftp/upload
    chown sftpuser:sftpuser /srv/sftp/upload
    
  3. Bind mount it to appear in chroot:
  4. mount --bind /srv/sftp/upload /home/sftpuser/upload
    
  5. Make the mount persistent (in /etc/fstab):
  6. /srv/sftp/upload /home/sftpuser/upload none bind 0 0
    

For systems where bind mounts aren't feasible:

# Create a writable directory
mkdir -p /home/sftpuser/.writable-root
chown sftpuser:sftpuser /home/sftpuser/.writable-root

# Create symlink that appears as root directory
ln -s /home/sftpuser/.writable-root /home/sftpuser/upload_area

Enhance your sshd_config with these parameters:

Match User sftpuser
    ChrootDirectory /home/sftpuser
    ForceCommand internal-sftp -u 0002
    PermitTunnel no
    AllowAgentForwarding no
    AllowTcpForwarding no
    X11Forwarding no

After implementation, test with:

sftp sftpuser@yourserver.com
sftp> put testfile.txt upload_area/
Uploading testfile.txt to /upload_area/testfile.txt
testfile.txt
  • Keep the chroot directory owned by root
  • Regularly audit mount points and permissions
  • Consider using filesystem quotas
  • Monitor for symlink attacks in shared environments

When setting up an SFTP server with chroot, we encounter a fundamental security constraint: The chroot directory (/home/sftpuser in this case) must be owned by root with strict permissions (typically 755). This creates a write-permission paradox - users can't write to their own chroot root directory despite it being their virtual filesystem root.

The existing setup shows all the proper security measures:


# /etc/ssh/sshd_config snippet
Match User sftpuser
    ChrootDirectory /home/sftpuser
    ForceCommand internal-sftp

Directory permissions:


/home/sftpuser - owned by root:root (drwxr-xr-x)
/home/sftpuser/sftp_share - owned by sftpuser:sftpuser (drwxrw-r-x)

Solution 1: Symlink Magic

Create a symlink from the chroot directory to the writable subdirectory:


sudo ln -s /home/sftpuser/sftp_share /home/sftpuser/uploads

Then modify authorized_keys:


command="cd /uploads && $SSH_ORIGINAL_COMMAND" ssh-rsa AAAAB3Nza...

Solution 2: Bind Mount Approach

Mount the writable directory at chroot level:


sudo mkdir /home/sftpuser/writable
sudo chown sftpuser:sftpuser /home/sftpuser/writable
sudo mount --bind /home/sftpuser/sftp_share /home/sftpuser/writable

Add to /etc/fstab for persistence:


/home/sftpuser/sftp_share /home/sftpuser/writable none bind 0 0

Solution 3: Alternative Directory Structure

Reorganize the entire hierarchy:


sudo mkdir -p /sftp_chroot/sftpuser_root
sudo chown root:root /sftp_chroot
sudo chmod 755 /sftp_chroot
sudo chown sftpuser:sftpuser /sftp_chroot/sftpuser_root

Update sshd_config:


Match User sftpuser
    ChrootDirectory /sftp_chroot

Now the user's effective root is writable.

While Solution 3 provides the cleanest implementation, consider:

  • Ensure no world-writable permissions exist
  • Regularly audit directory ownership
  • Implement disk quotas if needed
  • Consider using filesystem ACLs for finer control

After implementing any solution, verify with:


sftp sftpuser@yourserver.com
sftp> put testfile.txt
sftp> ls -l

For automated testing, use this expect script:


#!/usr/bin/expect -f
spawn sftp -oPort=22 sftpuser@yourserver.com
expect "sftp>"
send "put testfile.txt\r"
expect "sftp>"
send "ls -l\r"
expect "sftp>"
send "quit\r"