ovftool
is a command-line utility from VMware that lets one do useful things
with VMs on ESXi and vSphere remote systems.
I had installed ovftool
and was trying to use it copy a VM between two ESXi
servers, based on this useful post from virtuallyGhetto. For various
reasons, it’s often a better idea to use ovftool
for copying VMs than by just
using scp
on the raw files.
Immediately, I ran into a WTF? moment.
$ ovftool vi://root@10.1.2.3/
Enter login information for source vi://10.1.2.3/
Username: root
Password: *******
Error: Could not lookup host: root
Error: Could not lookup host: root
???
This confused the living daylights out of me. This has nothing at all to do with looking up a host.
Right?
Wrong.
And the answer is… drum roll…
Locators. At least, the URI-flavored ones. A locator points to different resource types like VMs or hosts.
When ovftool
gets a URI, it’s more or less of the form
protocol://username:password@host:port/
or protocol://username@host:port/
(protocol
can be one of the standard schemes like https
or file
, or
VMware-specific ones like vi
or vcloud
.)
If ovftool
gets a URI without the password - which I would imagine most
security conscious people would prefer - it quite sensibly prompts for a
password and captures it without displaying it.
At this point, it appears that ovftool
forms the full URI - including
password - and uses that to authenticate with the remote system.
You can see where this is going.
The ovftool
PDF manual clearly notes (but not clearly enough, in my view):
Encoding Special Characters in URL Locators
When you use URIs as locators, you must escape special characters using %
followed by their ASCII hex value. For instance, if you use a “@” in your
password, it must be escaped with %40 as in vi://foo:b%40r@hostname, and a
slash in a Windows domain name (\) can be specified as %5c.
Now I get it.
ovftool
captures the password from stdin and does not urlencode it.ovftool
forms the URI with the unencoded password, and does not check it for validity.ovftool
uses the URI to contact the remote system.- The malformed URI is interpreted such that the user name -
root
in this case - is considered to be the remote system’s host name. - The connection attempt dies with the oh-so-misleading message,
Error: Could not lookup host: root
. - I get a headache.
This hypothesis can be proven as follows.
- urlencode the failing password;
- Feed the encoded password to
ovftool
at thePassword:
prompt; - …
- Profit!!
What I think ovftool
does wrong
ovftool
violates The principle of Least Astonishment. When a tool accepts a password for input, it is expected that the tool does any necessary transformations to it prior to using it.ovftool
must urlencode the password if it obtains it via a password prompt.ovftool
fails to check that the URI is well-formed. The characters that must be urlencoded in the various parts of a URI are well-known, and it should be fairly easy to test this.- A more minor, yet valid quibble, is that
ovftool
echoes asterisks once the password has been entered, which is ok, except that it echoes the same number of asterisks as the number of characters in the password. Exact password length can give a useful clue to a would-be attacker (who would need to be looking over your shoulder, but still, it’s so easy to avoid this mistake).
A simple workaround
A simple workaround is to urlencode the password yourself on the command line. If you have access to a clipboard-copy-paste utility, the entire thing can be done without displaying the password.
Let’s say that your password is my@random pass/+?$#%^&*()-_+={}[]\|;:'”,/?
.
This code snippet will prompt for the unencoded password, urlencode it, and put it into the clipboard, ready for pasting. The advantage of using this over the various command-line urlencoding utilities - that might or might not be available - is that Python is available just about everywhere these days. If not, there’s always Perl.
python -c 'import urllib; import getpass; print(urllib.quote_plus(getpass.getpass()))' | $CLIPUTIL
Clipboard utilities
Possible values of $CLIPUTIL
include
- Windows 10:
clip
(or redirect to/dev/clipboard
in older versions) - macOS:
pbcopy
- Linux (X):
xclip -selection c