From 3c5bdde815c503f2461d24db0caddaac37527a14 Mon Sep 17 00:00:00 2001 From: Thomas Lamprecht Date: Thu, 18 Jun 2020 18:20:10 +0200 Subject: [PATCH] vncproxy: allow to request a generated VNC password We used the VNC API $ticket as password for VNC, but QEMU limits the password to the first 8 chars and ignores the rest[0]. As our tickets start with a static string (e.g., "PVE") the entropy was a bit limited. For Proxmox VE this does not matters much as the noVNC viewer provided by has to go always over the API call, and so a valid ticket and correct permissions for the requested VM are enforced anyway. This patch helps external users, which often use NoVNC-Websockify, circumventing the API and relying solely on the VNC password to avoid snooping on VNC sessions. A 'generate-password' parameter is added, if set a password from good entropy (using libopenssl) is generated. For simplicity of mapping random bits to ranges we extract 6 bit of entropy per character and add the integer value of '!' (first printable ASCII char) to that. This way we get 64^8 possibilities, which even with millions of guesses per second one would need years of guessing and mostly just DDOS the server with websocket upgrade requests. Signed-off-by: Thomas Lamprecht Tested-By: Dominik Csapak Reviewed-By: Dominik Csapak --- PVE/API2/Qemu.pm | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index 3e0d59f..dcb364d 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -7,6 +7,7 @@ use Net::SSLeay; use POSIX; use IO::Socket::IP; use URI::Escape; +use Crypt::OpenSSL::Random; use PVE::Cluster qw (cfs_read_file cfs_write_file);; use PVE::RRD; @@ -1589,6 +1590,19 @@ __PACKAGE__->register_method({ return undef; }}); +# uses good entropy, each char is limited to 6 bit to get printable chars simply +my $gen_rand_chars = sub { + my ($length) = @_; + + die "invalid length $length" if $length < 1; + + my $min = ord('!'); # first printable ascii + my @rand_bytes = split '', Crypt::OpenSSL::Random::random_bytes($length); + my $str = join('', map { chr((ord($_) & 0x3F) + $min) } @rand_bytes); + + return $str; +}; + my $sslcert; __PACKAGE__->register_method({ @@ -1610,6 +1624,12 @@ __PACKAGE__->register_method({ type => 'boolean', description => "starts websockify instead of vncproxy", }, + 'generate-password' => { + optional => 1, + type => 'boolean', + default => 0, + description => "Generates a random password to be used as ticket instead of the API ticket.", + }, }, }, returns => { @@ -1617,6 +1637,12 @@ __PACKAGE__->register_method({ properties => { user => { type => 'string' }, ticket => { type => 'string' }, + password => { + optional => 1, + description => "Returned if requested with 'generate-password' param." + ." Consists of printable ASCII characters ('!' .. '~').", + type => 'string', + }, cert => { type => 'string' }, port => { type => 'integer' }, upid => { type => 'string' }, @@ -1644,6 +1670,10 @@ __PACKAGE__->register_method({ my $authpath = "/vms/$vmid"; my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath); + my $password = $ticket; + if ($param->{'generate-password'}) { + $password = $gen_rand_chars->(8); + } $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192) if !$sslcert; @@ -1680,7 +1710,7 @@ __PACKAGE__->register_method({ '-perm', 'Sys.Console']; if ($param->{websocket}) { - $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm + $ENV{PVE_VNC_TICKET} = $password; # pass ticket to vncterm push @$cmd, '-notls', '-listen', 'localhost'; } @@ -1690,7 +1720,7 @@ __PACKAGE__->register_method({ } else { - $ENV{LC_PVE_TICKET} = $ticket if $websocket; # set ticket with "qm vncproxy" + $ENV{LC_PVE_TICKET} = $password if $websocket; # set ticket with "qm vncproxy" $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid]; @@ -1725,13 +1755,16 @@ __PACKAGE__->register_method({ PVE::Tools::wait_for_vnc_port($port); - return { + my $res = { user => $authuser, ticket => $ticket, port => $port, upid => $upid, cert => $sslcert, }; + $res->{password} = $password if $param->{'generate-password'}; + + return $res; }}); __PACKAGE__->register_method({ -- 2.39.2